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

上面的假设 3 将帮助您确定所需计算的块的实际大小。对于 malloc(9),相应的块(包括标题)将为 32 字节(标题为 16 + 假设 sizeof(size_t) == 8 的数据为 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 - 尼姆的游戏,minimax

有3堆(1堆-7场比赛,2堆-5场比赛,3堆-3场比赛)您可以拿任意数量的比赛,但只能从一堆比赛中取最后一场比赛的人输。https://en.wikipedia.org/wiki/Nim有一个代码可以...

memory - Memory 布局与 Memory 管理方案的混淆

我正在研究一些操作系统概念,有点困惑,现在有以下问题......正在执行的程序的memory布局(即文本、数据、堆栈、堆)是否仅在其虚拟地址空间的上下文中才有意义?如果一个程序被组织(“布局”)到它的...

最新文章