SecondLife
Last updated
Last updated
This is double free vulnerability in Doug Lea's malloc. This challenge is an example taken from Secure Coding in C and C++. A complete analysis of the example exists in the book (section 4.7, Double-Free Vulnerabilities), and this writeup is inspired by it. It is also recommended that you read the "Heap overflow" writeup before reading this one since it explains the basics of heap overflows. The double free vulnerability takes advantage of an overflow on the heap.
Double free vulnerability:
The double free vulnerability arises from freeing the same chunk of memory twice without its being reallocated between the two free operations.
Using this vulnerability, it is possible to write a DWORD (4 bytes) to an arbitrary memory location, as long as the following additional conditions are met:
The chunk to be freed must be isolated in memory (that is, the adjacent chunks must be allocated so that no consolidation takes place).
The bin into which the chunk is to be placed must be empty. An empty bin has forward and backward pointers that are self-referential.
Some other chunk needs to be freed after the double-free chunk is freed for the first time. This is because when a chunk is freed for the first time, it is moved to a special bin which acts as a "cache bin". When another allocation is performed, the chunk in the cache bin will be checked first and if it is compliant with the allocation requirements, it will be used. If, on the other hand, another chunk has been freed while a chunk is in the cache bin, the newly freed chunk will replace the older chunk in the cache bin and the older chunk will move to a regular bin. The attack requires the double-free chunk to be in a regular bin.
Empty bin and allocated memory chunk:
Structure after the memory chunk is freed for the first time:
When a chunk of memory is freed, it must be linked into the appropriate double-linked list. In dlmalloc
, this is performed by the frontlink
code segment:
The frontlink
code segment is executed after adjacent chunks are consolidated. The frontlink
function is still called if a chunk is not consolidated with its neighboring chunks. After the frontlink code segment executes, the bin’s forward and backward pointers reference the freed chunk, and the chunk’s forward and backward pointers reference the bin. This is the expected behavior, as we now have a double-linked list containing the free chunk.
However, if the memory chunk referenced is freed a second time, the data structure is corrupted. As shown below, the bin’s forward and backward pointers still reference the chunk, but the chunk’s forward and backward pointers become self-referential.
The bin's doubly-linked list still points to the chunk, but the chunk itself doesn't point back to the bin. So, the chunk can still be allocated, but it is impossible to actually remove it from the bin with unlink()
as part of the allocation process. Invoking the unlink()
macro to remove the chunk from the bin will leave the pointers unchanged. Instead of the chunk being removed from the bin, the data structures remain exactly as they appeared in the figure before the memory allocation request. This brings the heap manager to a state where it will continue to provide the same chunk for all future allocation requests which match the bin criteria.
Just pwn this program using a double free and get a flag. It's also found in /problems/secondlife_1_b2d4198bf562d945454af6ac9fa3d8ae on the shell server. Source.
How the vuln.c code meets the requirements for this exploit:
first=malloc(256);
(Line 22): allocate our target
second=malloc(256);
(Line 26): allocated and used to ensure that first
and third
will not be consolidated
third=malloc(256);
(Line 27): allocate and will be used later to move first
from the cache bin to a regular bin
fourth=malloc(256);
(Line 28): allocate and used to ensure that third
is not consolidated
free(first);
(Line 29): put first
into the cache bin. When moving to the bin, the heap manager tries to consolidate the first
chunk. However, the second
chunk is allocated so it cannot be consolidated.
free(third);
(Line 30): frees the third
chunk, thus moving the first chunk to a regular bin since third
is now in the cache bin. The allocation of the second
and fourth
chunks earlier prevents the third
chunk from being consolidated.
fifth=malloc(128);
(Line 31): allocates the fifth
chunk, which causes memory to be split off from the third
chunk, and as a side effect, the first
chunk is moved to a regular bin (its one chance to be reallocated from the cache bin has passed)
free(first);
(Line 32): memory is now configured so that freeing the first chunk a second time sets up the double-free vulnerability
How to exploit the double free vulnerability:
sixth=malloc(256);
(Line 33): allocates the sixth
chunk but malloc()
returns a pointer to the same chunk referenced by first
. This happens because sixth
is the same size as first (aka the same bin).
puts("You..."); gets(sixth);
(Lines 34-35): the GOT address of the exit()
function (minus 12) and the shellcode location are copied into this memory
seventh=malloc(256);
(Line 36): the same (as first
and sixth
) memory chunk is allocated yet again as the seventh
chunk. However, this time, when the chunk is allocated, the unlink()
macro copies the address of the shellcode into the address of the exit()
function in the global offset table (and overwrites a few bytes near the beginning of the shellcode).
exit(0);
(Line 37): the exit()
function is called and control is transferred to the shellcode
Recap: We allocate the memory block first
and free it twice without consolidation to a regular bin with no blocks already in it. Then, when we allocate the sixth
block and the heap manager tries to run the unlink()
macro on the unallocated first
block. However, the unlink does not work as intended since the forwards and backwards pointers in the first
block are self-referential. Thus, the first
block remains connected to the bin for first
's size and will always be returned when a size from that represents that bin is requested. We write the address in the GOT of some later function that is called (in this case exit()
) followed by the address of our shellcode. When the seventh
chunk is requested, the pointer to the first
chunk is returned again. But this time the forward and backward addresses have been overwritten. Thus, when the unlink()
macro runs, it will overwrite the GOT entry for the function (exit()
) with the address of our shellcode.
Payload code:
address
is the address of first
which is leaked (printed to terminal) by the program. We add 8
to this address because our shellcode is 8 bytes after the start of the first
block. The first 8 bytes contain the GOT address of exit()
(minus 12) and the address of the shellcode (plus 8). The "trampoline" jmp
is necessary as explained in the "Heap overflow" challenge.
Run the script.py to get the flag python script.py USER=<username> PASSWORD=<password>
:
picoCTF{HeapHeapFlag_f26c09e1}