在 C 中的当前进程的堆中搜索和编辑值

Searching and editing values in the heap of the current process in C

因此,在我们的大学作业中,我们被要求在不触及变量的情况下更改两个顺序 printf("%s", s1); printf("%s", s2); 函数的输出。目的是让我们在基于 Linux 的系统上使用进程内存布局的理论知识。

预期的输出是第一次 printf 调用输出 s1 和 s2 被 space(s) 分割,第二次的输出保持原始应用程序的预期. s1s2 的值和大小是已知的。

我最初的想法是 malloc(0) 并减去等于字符串长度的偏移量(块大小值 +1),然后将其转换为 char *。由于 2 个字符串值非常小(绝对小于 4KiB,这是页面大小),我希望只为堆分配一个区域,因此它是线性的。

但看起来我得到的值为零,这意味着我正在查看未初始化的内存,或者与我希望找到的字符串不同的东西。

下面是有问题的代码:

#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;
}

我对 heap_attack 的实施如下:

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
}

假设您正在 GNU/Linux 使用 glibc,这是最常见的设置,那么您可以做出一些假设来帮助您解决问题。

  1. 如您所说,两个分配的块将驻留在同一个新初始化的(线性)堆中,该堆通常跨越多个页面(几个 KB)。仅供参考:Linux x86 上的页面大小是 4K,而不是 4M。
  2. 在堆初始化之后,连续分配(malloc() 中间没有任何 free() 的调用)将分配连续的内存块,因此第一个分配的字符串将在第二个分配的字符串之前.
  3. 您可以通过查看 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 了解 glibc 分配器使用的结构,我在其中简要说明了 malloc_chunk.
  4. 的内部布局
  5. 通过查看源代码或通过测试,您可以注意到 malloc 的块实际上在大小上取整为 2 * size_t.
  6. 的倍数

假设 1 和 2(我们可以再次在此特定环境中做出)保证:

  • s2 > s1(即内存中s2s1之后)
  • 两个字符串之间应该恰好有(s2 - s1 - strlen(s2) - 1个字节,除非strlen(s2)改变,否则这个值不会改变。
  • 下一个分配 malloc(x) 将在 s2 之后,并且始终与 s2 保持相同的固定偏移量,您可以轻松计算一次然后使用(假设 s2保持相同的长度)。

上面的假设 3 将帮助您计算出所需计算的块的实际大小。对于 malloc(9),相应的块(包括 header)将是 32 个字节(16 个用于 header + 16 个用于假设 sizeof(size_t) == 8 的数据)。此外 malloc_usable_size() 将为您提供不包括 header.

的确切大小

用空格填充这些字节将完成您想要的。但是,在这样做时,您将销毁 s1 的块 header,并且以后尝试释放(损坏的)块很可能会导致崩溃。你可以在你的情况下完全避免 free() 因为你真的不需要它。操作系统无论如何都会在程序终止时回收内存。

您的 heap_attack() 的可能实施方式是:

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
}
#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;
}

如果我们选择堆解决方案:

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 = ' ';
    }
}