使用格式字符串覆盖任意内存
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]
赋值为128
,canary[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
我在阅读一篇关于 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]
赋值为128
,canary[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