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



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

预期的输出是第一个printf调用输出按空间划分的s1和s2,第二个调用的输出保持原始应用程序的预期。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: %sn", s1);
printf("student 2: %sn", 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
}

假设您正在使用glibc(最常见的设置)开发GNU/Linux,那么您可以做出一些有助于解决问题的假设。

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

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

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

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

用空格填充这些字节将实现您的愿望。然而,在这样做的过程中,您将破坏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
}
#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 - %luns1 - %pn", 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 - %pn", s1, s2);
if (s1 == NULL || s2 == NULL) return EXIT_FAILURE;
strcpy(s1, "s0000000");
strcpy(s2, "s1111111");
heap_attack();
printf("student 1: %sn", s1);
printf("student 2: %sn", 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 = ' ';
}
}

最新更新