I am trying to understand how the malloc implementation in glibc works. According to the malloc source code (malloc.c in glibc 2.23), free chunks of memory have the following structure.
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of previous chunk | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ `head:' | Size of chunk, in bytes |P| mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Forward pointer to next chunk in list | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Back pointer to previous chunk in list | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Unused space (may be 0 bytes long) . . . . | nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ `foot:' | Size of chunk, in bytes | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Usually we should also see this structure in the gnu (gdb) debugger. So I wrote the following program. The program allocates 6 memory blocks of 64 bytes in size. Each piece is filled with a memset, so we can easily see the corresponding piece in gdb later. As pieces 1,3 and 6 are freed, they must have the above structure. Since chunks are allocated between them, the freed chunks cannot be consolidated, and as a result, they are organized into a doubled linked list through pointers in each fragment.
#include <stdio.h> #include <stdlib.h> #include <string.h> void to_jump(); int main(int argc, char **argv){ char *b1, *b2, *b3, *b4, *b5, *b6; //allocate 6 chunks of memory b1 = malloc(64); b2 = malloc(64); b3 = malloc(64); b4 = malloc(64); b5 = malloc(64); b6 = malloc(64); memset(b1, 'B', 64); memset(b2, 'C', 64); memset(b3, 'D', 64); memset(b5, 'E', 64); memset(b6, 'F', 64); //free chunks 1,3 and 6 free(b1); free(b3); free(b6); strcpy(b4, argv[1]); // <-- set breakpoint here //exploit this line free(b4); free(b5); free(b2); } void to_jump(){ printf("Exploited"); }
When I run the program in gdb and set a breakpoint on the strcpy(b4, argv[1]); line strcpy(b4, argv[1]); we should see that the freed chunks are organized on a double linked list. However, the output of gdb is as follows:
gdb-peda$ p b1 $11 = 0x602010 "" gdb-peda$ x/62xg 0x602000 0x602000: 0x0000000000000000 0x0000000000000051 0x602010: 0x0000000000000000 0x4242424242424242 | 0x602020: 0x4242424242424242 0x4242424242424242 | b1 (freed) 0x602030: 0x4242424242424242 0x4242424242424242 | 0x602040: 0x4242424242424242 0x4242424242424242 | 0x602050: 0x0000000000000000 0x0000000000000051 0x602060: 0x4343434343434343 0x4343434343434343 | 0x602070: 0x4343434343434343 0x4343434343434343 | b2 (allocated) 0x602080: 0x4343434343434343 0x4343434343434343 | 0x602090: 0x4343434343434343 0x4343434343434343 | 0x6020a0: 0x0000000000000000 0x0000000000000051 0x6020b0: 0x0000000000602000 0x4444444444444444 | 0x602000 is pointing to b1 (previous freed block) 0x6020c0: 0x4444444444444444 0x4444444444444444 | b3 (freed) 0x6020d0: 0x4444444444444444 0x4444444444444444 | 0x6020e0: 0x4444444444444444 0x4444444444444444 | 0x6020f0: 0x0000000000000000 0x0000000000000051 0x602100: 0x0000000000000000 0x0000000000000000 | 0x602110: 0x0000000000000000 0x0000000000000000 | b4 (will be filled trough strcpy(b4, argv[1]); 0x602120: 0x0000000000000000 0x0000000000000000 | 0x602130: 0x0000000000000000 0x0000000000000000 | 0x602140: 0x0000000000000000 0x0000000000000051 0x602150: 0x4545454545454545 0x4545454545454545 | 0x602160: 0x4545454545454545 0x4545454545454545 | b5 (allocated) 0x602170: 0x4545454545454545 0x4545454545454545 | 0x602180: 0x4545454545454545 0x4545454545454545 | 0x602190: 0x0000000000000000 0x0000000000000051 0x6021a0: 0x00000000006020a0 0x4646464646464646 | 0x6020a0 is pointing to b3 (previous freed block) 0x6021b0: 0x4646464646464646 0x4646464646464646 | b6 (freed) 0x6021c0: 0x4646464646464646 0x4646464646464646 | 0x6021d0: 0x4646464646464646 0x4646464646464646 | 0x6021e0: 0x0000000000000000 0x0000000000020e21
In this output, we can see the freed chunks and a backward pointer to the previous freed fragment (see comments to the right of the output). But where are the pointers and size of the previous fragment?