使用格式字符串覆盖任意内存

Overwriting of Arbitrary memory using format string

我在阅读一篇关于 2000 年代格式字符串利用的旧文章,link 可以在这里找到:Article

在第 15 页,作者描述了通过增加 printf 内部堆栈指针来覆盖变量内容的方法:Stack pushed

    unsigned char   canary[5];
    unsigned char   foo[4];
    memset (foo, ’\x00’, sizeof (foo));

    /* 0 * before */ strcpy (canary, "AAAA");

    /* 1 */  printf ("%16u%n", 7350, (int *) &foo[0]);
    /* 2 */  printf ("%32u%n", 7350, (int *) &foo[1]);
    /* 3 */  printf ("%64u%n", 7350, (int *) &foo[2]);
    /* 4 */  printf ("%128u%n", 7350, (int *) &foo[3]);

    /* 5 * after */ printf ("%02x%02x%02x%02x\n", foo[0], foo[1],
            foo[2], foo[3]);

    printf ("canary: %02x%02x%02x%02x\n", canary[0],
            canary[1], canary[2], canary[3]);

Returns 输出“10204080”和“canary: 00000041”

不幸的是,作者没有解释堆栈被压入的原因,换句话说,printf 过程的哪一部分引起了内存中的覆盖?

编辑: 我明白 /1/ 中的指令将创建一个宽度为 16 的右填充字段,然后将写入的字节数 (16) 写入 foo[0] 的地址。 问题是为什么它会覆盖到相邻的内存?你通常会认为它只会写在 foo[0] 的地址上,这是一个字节而不是 4.

在此处阅读关于 %n 格式参数的 gnu lib c 之后:Gnu C

它声明 %n 使用的参数必须是指向 int 的指针。

根据我的理解,int 是(编辑)至少 16 位长,但根据编译器的不同,它可以存储为 4 字节字,在现代机器中甚至可以存储为 8 字节字.

我猜测这篇文章是在编译器已经将 int 存储为 4 字节字的时候写的,因此 %n 会将每个 unsigned char 指针提升为 int 指针,从而覆盖 4 字节的每次调用的内存,从字符地址开始。

why does it overwrite to the adjacent memory?

未定义的行为 (UB)。

以下所有行均显示 UB。 OP 看到的潜在结果,但 C 未指定。

/* 0 * before */ strcpy (canary, "AAAA");           // Writing out of bounds
/* 1 */  printf ("%16u%n", 7350, (int *) &foo[0]);  // Writing out of bounds, alignment issues.
/* 2 */  printf ("%32u%n", 7350, (int *) &foo[1]);
/* 3 */  printf ("%64u%n", 7350, (int *) &foo[2]);
/* 4 */  printf ("%128u%n", 7350, (int *) &foo[3]);

该代码具有未定义的行为并且无效。

无论如何,声明:

printf ("%16u%n", 7350, (int *) &foo[0]);

基本上是在做:

*(int *)&foo[0] = 16;

最大的问题是最后一个:

printf("%128u%n", 7350, (int *) &foo[3]);

正在做:

*(int *)&foo[3] = 128;

foo[3] 是一个无符号字符。假设 sizeof(int) = 4,即。 int 有 4 个字节,然后这将 3 个字节越界写入 foo + 3。 x86 以相反的顺序存储堆栈 - 为 canary 保留的内存放在 之后 foo 的内存。堆栈内存如下所示:

   <-- foo ---><--- canary ----->
   [0][1][2][3][0][1][2][3][4][5]
            ^^^^^^^^^^^^ 
                 storing (int)128 here in **little endian**

因为x86是little endian,所以foo[3]赋值为128canary[0..2]归零(因为128 = 0x00000080)。

你可以这样做:

// I want it to print 0xDEAD
// I swap bytes for endianess I get 0xADDE
// I then shift it left by 8 bytes and get 0xADDE00
// 0xADDE00 = 11394560
// The following printf will do foo[3] = 0x00
// but also: canary[0] = 0xDE, canary[1] = 0xAD and canary[2] = 0x00
fprintf("%11394560u%n", 7350, (int *) &foo[3]);
printf("0x%02x%02x\n", canary[0], canary[1]);
// will output 0xDEAD