Dynamic Memory Vulnerabilities
Stack data lasts per function while heap memory is there until freed.
Kernel Space
Stack
Memory Mapping Region
Heap
BSS Segment
Data Section
Text Segment (ELF)
...
For Linux, use the GNU Libc implementation
Potentially create a dynamic allocation around it, but not ideal.
Various Dynamic Allocators Implementations
Note: Only for small allocations. For large allocations, it uses mmap.
int main()
{
char *some_data = malloc(256);
scanf("%s", some_data);
printf("%s\n", some_data);
char really_long_string[512] = {'A'}
strncpy(
some_data,
really_long_string,
strnlen(really_long_string, 512));
free(some_data);
return 0;
}
int main()
{
char *some_data = malloc(256);
scanf("%s", some_data);
printf("%s\n", some_data);
// Make sure it is freed.
free(some_data);
scanf("%s", some_data);
if (strncmp(some_data, PASSWORD, 256))
{
printf("I am authenticated\n");
}
return 0;
}
int main()
{
char *some_data = malloc(256);
scanf("%s", some_data);
printf("%s\n", some_data);
/* You put code here */
/* End of your code */
return 0;
}
int main()
{
char *some_data = malloc(256);
scanf("%s", some_data);
printf("%s\n", some_data);
free(some_data);
free(some_data);
return 0;
}
int vuln()
{
char *buffer;
int size = 0;
printf("Size of buffer: ");
scanf("%d", &size);
// Allocating Memory
buffer = malloc(size);
printf("Input: ");
scanf("%s", buffer);
printf("%s\n", buffer);
// What happens here?
free(buffer);
printf("Print again: %s\n", buffer);
return 0;
}
int vuln()
{
char *buffer;
int size = 0;
printf("Size of buffer: ");
scanf("%d", &size);
// Allocating Memory
buffer = malloc(size);
printf("Input: ");
scanf("%s", buffer);
printf("%s\n", buffer);
// What happens here?
free(buffer);
buffer = NULL;
printf("Print again: %s\n", buffer);
return 0;
}
int vuln()
{
char *buffer;
int size = 0;
printf("Size of buffer: ");
scanf("%d", &size);
// Allocating Memory
buffer = malloc(size);
printf("Input: ");
scanf("%s", buffer);
printf("%s\n", buffer);
// What happens here?
free(buffer);
free(buffer);
return 0;
}
Bit 0: PREV_IN_USE
Bit 1: IS_MMAPPED
Bit 2: NON_MAIN_ARENA
unsigned long mchunk_prev_size;
unsigned long mchunk_size;
Dynamically Allocated Memory Chunk
chunk_addr
mem_addr:
prev_size
prev_size
size
a[0]
a[1]
size
b[0]
b[1]
chunk1: int *a = malloc(0x8)
chunk1: int *b = malloc(0x8)
Note: Only applies if PREV_INUSE flag is set
prev_size
prev_size
size
a[0]
a[1]
size
b[0]
b[1]
chunk1: int *a = malloc(0x8)
chunk1: int *b = malloc(0x8)
Note: Only applies if PREV_INUSE flag is set
unsigned long mchunk_prev_size;
unsigned long mchunk_size;
Dynamically Allocated Memory Chunk
b[1]
unsigned long mchunk_prev_size;
unsigned long mchunk_size;
Cache-Specific Metadata
Bin #
prev_size
Chunk Size
FD Pointer
BK Pointer
Unused Space
prev_size
Chunk Size
FD Pointer
BK Pointer
Unused Space
Bin #
prev_size
Chunk Size = 10
FD Pointer
BK Pointer
Unused Space
prev_size
Chunk Size = 10
FD Pointer
BK Pointer
Unused Space
Bin #
prev_size
Chunk Size = 10
FD Pointer
BK Pointer
Unused Space
prev_size
Chunk Size = 64
FD Pointer
BK Pointer
Unused Space
Bin #
prev_size
Chunk Size = 10
FD Pointer
BK Pointer
Unused Space
prev_size
Chunk Size = 64
FD Pointer
BK Pointer
Unused Space
Credit to pwn.college
Credit to pwn.college
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
typedef struct tcache_entry
{
struct tcache_entry *next;
struct tcache_perthread_struct *key;
} tcache_entry;
a = malloc(16);
b = malloc(16);
c = malloc(32);
d = malloc(32);
free(b);
free(a);
free(c);
free(d);
tcache_perthread_struct -- Mike
tcache_perthread_struct -- Mike
counts:
entries:
count_16 = 0
count_32 = 0
entry_16 = NULL
entry_32 = NULL
tcache entry B
next: NULL
Key: NULL
tcache entry C
next: NULL
Key: NULL
tcache entry A
next: NULL
Key: NULL
tcache entry D
next: NULL
Key: NULL
a = malloc(16);
b = malloc(16);
c = malloc(32);
d = malloc(32);
free(b);
free(a);
free(c);
free(d);
tcache_perthread_struct -- Mike
tcache_perthread_struct -- Mike
counts:
entries:
count_16 = 1
count_32 = 0
entry_16 = B
entry_32 = NULL
tcache entry B
next: NULL
Key: Mike
tcache entry C
next: NULL
Key: NULL
tcache entry A
next: NULL
Key: NULL
tcache entry D
next: NULL
Key: NULL
a = malloc(16);
b = malloc(16);
c = malloc(32);
d = malloc(32);
free(b);
free(a);
free(c);
free(d);
tcache_perthread_struct -- Mike
tcache_perthread_struct -- Mike
counts:
entries:
count_16 = 2
count_32 = 0
entry_16 = B
entry_32 = NULL
tcache entry B
next: A
Key: Mike
tcache entry C
next: NULL
Key: NULL
tcache entry A
next: NULL
Key: Mike
tcache entry D
next: NULL
Key: NULL
a = malloc(16);
b = malloc(16);
c = malloc(32);
d = malloc(32);
free(b);
free(a);
free(c);
free(d);
tcache_perthread_struct -- Mike
tcache_perthread_struct -- Mike
counts:
entries:
count_16 = 2
count_32 = 1
entry_16 = B
entry_32 = C
tcache entry B
next: A
Key: Mike
tcache entry C
next: NULL
Key: Mike
tcache entry A
next: NULL
Key: Mike
tcache entry D
next: NULL
Key: NULL
a = malloc(16);
b = malloc(16);
c = malloc(32);
d = malloc(32);
free(b);
free(a);
free(c);
free(d);
tcache_perthread_struct -- Mike
tcache_perthread_struct -- Mike
counts:
entries:
count_16 = 2
count_32 = 2
entry_16 = B
entry_32 = C
tcache entry B
next: A
Key: Mike
tcache entry C
next: D
Key: Mike
tcache entry A
next: NULL
Key: Mike
tcache entry D
next: NULL
Key: Mike
a2 = malloc(16);
b2 = malloc(16);
c2 = malloc(32);
d2 = malloc(32);
tcache_perthread_struct -- Mike
tcache_perthread_struct -- Mike
counts:
entries:
count_16 = 2
count_32 = 2
entry_16 = B
entry_32 = C
tcache entry B
next: A
Key: Mike
tcache entry C
next: D
Key: Mike
tcache entry A
next: NULL
Key: Mike
tcache entry D
next: NULL
Key: Mike
a2 = malloc(16);
b2 = malloc(16);
c2 = malloc(32);
d2 = malloc(32);
tcache_perthread_struct -- Mike
tcache_perthread_struct -- Mike
counts:
entries:
count_16 = 1
count_32 = 2
entry_16 = A
entry_32 = C
tcache entry B
next: NULL
Key: NULL
tcache entry C
next: D
Key: Mike
tcache entry A
next: NULL
Key: Mike
tcache entry D
next: NULL
Key: Mike
a2 = malloc(16);
b2 = malloc(16);
c2 = malloc(32);
d2 = malloc(32);
tcache_perthread_struct -- Mike
tcache_perthread_struct -- Mike
counts:
entries:
count_16 = 0
count_32 = 2
entry_16 = NULL
entry_32 = C
tcache entry B
next: NULL
Key: NULL
tcache entry C
next: D
Key: Mike
tcache entry A
next: NULL
Key: NULL
tcache entry D
next: NULL
Key: Mike
a2 = malloc(16);
b2 = malloc(16);
c2 = malloc(32);
d2 = malloc(32);
tcache_perthread_struct -- Mike
tcache_perthread_struct -- Mike
counts:
entries:
count_16 = 0
count_32 = 1
entry_16 = NULL
entry_32 = D
tcache entry B
next: NULL
Key: NULL
tcache entry C
next: NULL
Key: NULL
tcache entry A
next: NULL
Key: NULL
tcache entry D
next: NULL
Key: Mike
a2 = malloc(16);
b2 = malloc(16);
c2 = malloc(32);
d2 = malloc(32);
tcache_perthread_struct -- Mike
tcache_perthread_struct -- Mike
counts:
entries:
count_16 = 0
count_32 = 0
entry_16 = NULL
entry_32 = NULL
tcache entry B
next: NULL
Key: NULL
tcache entry C
next: NULL
Key: NULL
tcache entry A
next: NULL
Key: NULL
tcache entry D
next: NULL
Key: NULL
Carefully creating a region of memory with heap-metadata in it can trick the heap allocator to think it's another chunk of memory!
What are some things we might need to include in the metadata structure?