c - 在C中当前进程的堆中搜索和编辑values

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

预期的输出是第一次 printf 调用输出 s1 和 s2 按空格分隔,第二次的输出保持原始应用程序的预期。 values 和 s1s2 的大小是已知的。

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

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

以下是有问题的代码:

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

回答1

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

  1. 正如您所说,两个分配的块都将驻留在同一个新初始化的(线性)堆中,该堆通常跨越多个页面(几个 KB)。仅供参考:Linux x86 上的页面大小是 4K,而不是 4M。
  2. 在堆初始化之后,连续分配(malloc() 调用中间没有任何 free())将分配连续的内存块,因此第一个分配的字符串将在第二个分配的字符串之前。
  3. 您可以通过查看 https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L1075 了解 glibc 分配器使用的结构(选择正确的版本,运行 /lib/x86_64-linux-gnu/libc.so.6 将打印版本)。您还可以查看https://stackoverflow.com/a/62096458/3889449,我在其中简要解释了 malloc_chunk 的内部布局。
  4. 通过查看源代码或通过测试,您可以注意到 malloc 的块实际上在大小上四舍五入为 2 * size_t 的倍数。

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

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

上面的假设 3 将帮助您计算出所需计算的块的实际大小。对于 malloc(9),相应的块(包括标头)将为 32 个字节(假设为 sizeof(size_t) == 8,标头为 16 个,数据为 16 个)。此外 https://manned.org/malloc_usable_size.3 将为您提供不包括标题的确切大小。

用空格填充这些字节将完成您想要的。但是,在这样做时,您将破坏 s1 的块标头,并且以后尝试释放(损坏的)块很可能会导致崩溃。你可以在你的情况下完全避免 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
}

回答2

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

相似文章

python - 无限 while 和 for loop Python

我的代码似乎陷入了无限循环。它应该遍历能量曲线,其中2个局部最小值之间的每个点都被添加到子列表teil_reaction中,然后移动到下一组点。此外,前一个子列表的最后一个点也是下一个子列表的第一个点...

随机推荐

最新文章