There are some cases about heap you want.
0x01 System call - mmap, brk
- malloc get memory firstly
- system calls - ask the kernel directly
- mmap()
- ask kernel give us some new virtual addresses
- brk()
- change data segment size
- mmap()
process doesn’t care how the memory is implemented.
allocated memory through Linux Kernel
and CPU's MMU
etc map to process. Then process can transparently access these memory.
In CTF match, we use very little space to malloc, so brk system call doesn’t appear.
How malloc work?
0x02 malloc implement
Easily we consider the large region as heap.
Now, we want a size 8 memory to write some chars like AAAABBBB
. use malloc(8)
. Then return a address can be writed.
1 | +—————————+—————————+—————————+—————————+ <- 0x804000 |
If malloc(8)
again, what will happen
guess
1 | +—————————+—————————+—————————+—————————+ <- 0x804000 |
if this is reality, some questions here.
- how does malloc know what address to return?
- how does malloc know which areas are still free?
some implement here.
DLmalloc
most common use in malloc.
workflow
- store for each chunk it blocks the size of chunk right before it.
- keep 4 bytes before chunk (before freed)
1 | ret_addr: 0x804008 |
first malloc return address can be calculated equal 0x804000+0x8=0x804008
Then, how does malloc know the next address after called where the next chunk can be placed?
If start of the free/available area: 0x804000, next chunk address at start + now_chunk_size, so at 0x804000 + 0x10(size) = 0x804010. The will happen at malloc of system calls.
So there is a point somewhere at always point to a free memory.
Given the returned address of malloc, what can we do with that?
- Writable data addr: this pointer points to the start where we can write data to.
- Size of chunk: before writable data addr.
- Next chunk addr:
start of current chunk addr
addsize of this chunk
0x03 small code cases
heap1.c
1 |
|
- objective -> winner
- struct internet
- first member: priority, (type): int
- second member: name, (type): char point (means contain a point to a string somewhere else)
*
pointer(meaning: contains an address)
In 32-Bit machine, point size(a address size) equal 4 bits, but 8 bits in 64-Bit machine.
malloc(sizeof(struct internet))
So a chunk size: 4(int) + 4(char *) = 8 (tests in 32Bit) i1->priority = 1;
write 1 to the first 4 byte of the allocated area.
1 | int priority char *name |
name point at offset = 4 of i1 object(name point at: i1 + 4).i1->name = malloc(8);
Then it allocates another 8 byte, and the resulting address will stored in the char point name. Those 8 bytes are intended to store a list of characters.
As a programmer, we use i1->name
to access name member property. As simply going to a certain offset of i1, in this case i1+4 is the location of the char point name. i1->name [(0x804d198+8)+4]
. [num]
express access address in the num address which means fetch info indirectly.
1 | pwndbg> heap |
i2 looks like i1.
We enable the argv[1] = "aaaabbbb", argv[2] = "aaaabbbb";
, so heap info behind.
1 | pwndbg> r aaaabbbb aaaabbbb |
a dangerous function! strcpy
strcpy
has no length check. So we can overflow name when write over 8 byte and really screw up stuff.
size low bit indicate that the PREVIOUS chunk is used, so we find it is 0x11 not 0x10. That becomes more important for the free()
The dlmalloc is not really the original dlmalloc. It is usually referred to as ptmalloc.
0x04 overwrite
heap overflow works not well. overwrite somewhat? It actually is called got overwrite.
GOT - _GLOBAL_OFFSET_TABLE_
: record function offset in program running.
function call use the plt addr.
1 | -> 0x08049312 <+192>: call 0x80490e0 <puts@plt> |
we need pad some chars to overwrite the i2 name point address.
it looks like this.
If we write on i2 name, what will happen?
1 | pwndbg> x 0x804c01c |
so payload
or