Home > Software engineering >  Searching and editing values in the heap of the current process in C
Searching and editing values in the heap of the current process in C

Time:05-19

So in our university assignment we were asked to change the output of two sequential printf("%s", s1); printf("%s", s2); functions without touching the variables. The intent was for us to use the theoretical knowledge of the memory layout of the process on Linux based systems.

The expected output is for the first printf call to output both s1 and s2 split by space(s), and for the output of the second to remain as intended by the original application. The values and sizes of s1, and s2 are known.

My initial idea was to malloc(0) and subtract the offset equal to the length of the string ( 1 for the chunk size value) , and then cast that as a char *. Since the 2 string values are very small (definitely less than 4KiB, which is the page size) I was hoping that there'd be just one region allocated for heap, and hence it is linear.

But from what it seems I get values of zero, which means I'm looking through an un-initialized memory, or something different then the strings I hoped to locate.

Below is the code in question:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void heap_attack() { 
    // alternatively it can have signature of 
    // void heap_attack(void * v);
    // but the task is assumed to be solvable with the original signature
    
}

int main(int argc, char const *argv[])
{
    char * s1 = malloc(9);
    char * s2 = malloc(9);

    if (s1 == NULL || s2 == NULL) return EXIT_FAILURE;

    strcpy(s1, "s0000000");
    strcpy(s2, "s1111111");

    heap_attack();

    printf("student 1: %s\n", s1);
    printf("student 2: %s\n", s2);

    free(s1);
    free(s2);

    return 0;
}

My implementation of the heap_attack begins as follows:

void heap_attack() {
  char * heap_end = malloc(0) - 1; // 1 for the size fragment preceding writable space
  char * s1 = heap_end - 9;
  printf("%s", s1); // here i expected to see the s1111111 string printed to stdout
}

CodePudding user response:

Assuming that you are working on GNU/Linux using glibc, which is the most common setup, then you can make some assumptions that will help you solve the problem.

  1. As you say, both allocated chunks will reside into the same newly initialized (linear) heap, which usually spans multiple pages (several KBs). FYI: page size on Linux x86 is 4K, not 4M.
  2. Right after initialization of the heap, consecutive allocations (malloc() calls without any free() in the middle) will allocate contiguous chunks of memory, so the first allocated string will be right before the second.
  3. You can know the structure used by the glibc allocator by looking at the source code (pick the correct version, running /lib/x86_64-linux-gnu/libc.so.6 will print the version). You can also look at this other answer of mine where I briefly explain the internal layout of a malloc_chunk.
  4. Either by looking at the source code or by testing, you can notice that malloc'd chunks are actually rounded up in size to multiples of 2 * size_t.

Assumptions 1 and 2 (which again we can make in this specific environment) guarantee that:

  • s2 > s1 (that is, s2 is in memory after s1)
  • There should be exactly (s2 - s1 - strlen(s2) - 1 bytes between the two strings, and this value will not change unless strlen(s2) changes.
  • The next allocation malloc(x) will be after s2, and always at the same fixed offset from s2, which you can easily calculate once and then use (assuming s2 keeps the same length).

Assumption 3 above will help you figure out the actual size of the chunks for the calculations you need. For malloc(9) the corresponding chunk (header included) would be 32 bytes (16 for header 16 for data assuming sizeof(size_t) == 8). Furthermore malloc_usable_size() will give you the exact size excluding the header.

Filling up those bytes with spaces will accomplish what you wish for. However, in doing so, you will destroy the chunk header for s1, and any later attempt at freeing the (corrupted) chunk will most likely cause a crash. You can avoid free() altogether in your case since you don't really need it. The operating system will reclaim memory anyway on program termination.

A possible implementation of your heap_attack() would be:

void heap_attack(void) {
    char *top_chunk = malloc(1);
    char *top_chunk_header = top_chunk - 2 * sizeof(size_t);
    char *s2 = top_chunk_header - 16;       // assuming strlen(s2) <= 15
    char *s1 = s2 - 2 * sizeof(size_t) - 16 // assuming strlen(s1) <= 15;

    // Fill memory between s1   8 and s2 with spaces
}

CodePudding user response:

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#define STRING_DISTANCE         (0x20)
#define ARBITRARY_STACK_OFFSET  (20)

// The strategy is as follows: We can tell the difference from the contigous heap blocks (0x20) in advance (at least for the same machine)
// So given s1 - we would like to simply overwrite everything between with spaces - that way when it prints s1, it will print spaces until s2 and its null terminator
// The only challenge is to find s1, the way we do that here is by simply traveling up the stack and finding our two strings, assuming we know their offsets.
void heap_attack()
{ 
    size_t a = 0;
    void * s1 = 0;

    for (uintptr_t * stack_ptr = &a; a < ARBITRARY_STACK_OFFSET; a  = 1)    // Travel up the stack, from a variable in our frame, to the function calling us.
    {
        if (stack_ptr[a] - (uintptr_t)s1 == STRING_DISTANCE)
        {
            printf("stack offset - %lu\ns1 - %p\n", a, (void *)stack_ptr[a]);
            break;
        }

        s1 = stack_ptr[a];
    }

    for (char * x = (char *)s1   strlen(s1); x < s1   STRING_DISTANCE; x  = 1)
    {
        *x = ' ';
    }
}

int main()
{
    char * s1 = malloc(9);
    char * s2 = malloc(9);

    printf("%p - %p\n", s1, s2);

    if (s1 == NULL || s2 == NULL) return EXIT_FAILURE;

    strcpy(s1, "s0000000");
    strcpy(s2, "s1111111");

    heap_attack();

    printf("student 1: %s\n", s1);
    printf("student 2: %s\n", s2);

    // I disabled the frees because I corrupted the blocks, corrupting the blocks is neccessary so it would print spaces between them
    // If we don't want to corrupt the blocks, another option would be to also override the format string, but that's outside the scope of this challenge imo
    // free(s1);
    // free(s2);


    return 0;
}

And if we choose the heap solution:

void heap_attack()
{ 
    char * s1 = (char *)malloc(9) - STRING_DISTANCE * 2;

    for (char * x = (char *)s1   strlen(s1); x < s1   STRING_DISTANCE; x  = 1)
    {
        *x = ' ';
    }
}
  • Related