c - 理解格式字符串利用的困难
问题描述
我正在读一本书,Hacking: The Art of Exploitation 2nd Edition,我正在阅读格式字符串漏洞的章节。我多次阅读该章节,但即使使用谷歌搜索,我也无法清楚地理解它。
所以,书中有这个易受攻击的代码:
char text[1024];
...
strcpy(text, argv[1]);
printf("The right way to print user-controlled input:\n");
printf("%s", text);
printf("\nThe wrong way to print user-controlled input:\n");
printf(text);
然后编译后,
reader@hacking:~/booksrc $ ./fmt_vuln $(perl -e 'print "%08x."x40')
The right way to print user-controlled input:
%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.
%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.
%08x.%08x.
The wrong way to print user-controlled input:
bffff320.b7fe75fc.00000000.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.30252
e78.252e7838.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.30252e78.2
52e7838.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.30252e78.252e78
38.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.
字节 0x25、0x30、0x38、0x78 和 0x2e 似乎重复了很多。
reader@hacking:~/booksrc $ printf "\x25\x30\x38\x78\x2e\n"
%08x.
首先,为什么这种价值会重演?
如您所见,它们是格式字符串本身的内存。因为格式化函数总是在最高的堆栈帧上,只要格式化字符串被存储在堆栈的任何位置,它就会位于当前帧指针的下方(在更高的内存地址)。
但在我看来,这与他之前写的内容以及堆栈帧的组织方式相矛盾
当这个 printf() 函数被调用(与任何函数一样)时,参数以相反的顺序被压入堆栈。
那么,格式字符串不应该位于较低的内存地址,因为它是第一个参数吗?格式字符串存储在哪里?
reader@hacking:~/booksrc $ ./fmt_vuln AAAA%08x.%08x.%08x.%08x
The right way to print user-controlled input:
AAAA%08x.%08x.%08x.%08x
The wrong way to print user-controlled input:
AAAAbffff3d0.b7fe75fc.00000000.41414141
再次,为什么AAAA
在41414141
. 据我了解,该printf
函数首先打印AAAA
,然后当它看到第一个时%08x
,它从前面的堆栈帧中的内存地址获取一个值,然后对第二个执行相同操作%08x
,因此第二个的值位于内存中地址高于第一个,最后返回函数AAAA
栈帧中位于较低内存地址的printf
值。
$(perl -e 'print "%08x."x40')
我用as 参数调试了第一个示例。我运行:Linux 5.3.0-40-generic、18.04.1-Ubuntu、x86_64
(gdb) run $(perl -e 'print "%08x." x 40')
Starting program: /home/kuro/fmt_vuln $(perl -e 'print "%08x." x 40')
The right way to print user-controlled input:
%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.
The wrong way to print user-controlled input:
07a51260.4b3eb8c0.4b10e154.00000000.4b16c3a0.9d357fc8.9d357b10.78383025.30252e78.2e783830.3830252e.252e7838.78383025.30252e78.2e783830.3830252e.252e7838.78383025.30252e78.2e783830.3830252e.252e7838.78383025.30252e78.2e783830.3830252e.252e7838.78383025.30252e78.2e783830.3830252e.252e7838.4b618d00.4b5fd000.00000000.9d357c80.00000000.00000000.00000000.4b3ef6f0.
Breakpoint 1, main (argc=2, argv=0x7ffd9d357fc8) at fmt_vuln.c:19
19 printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val, test_val);
(gdb) x/-100xw $rsp
0x7ffd9d357940: 0x00000400 0x00000000 0x4b07c1aa 0x00007fb8
0x7ffd9d357950: 0x00000016 0x00000000 0x00000003 0x00000000
0x7ffd9d357960: 0x00000001 0x00000000 0x00002190 0x000003e8
0x7ffd9d357970: 0x00000005 0x00000000 0x00008800 0x00000000
0x7ffd9d357980: 0x00000000 0x00000000 0x00000400 0x00000000
0x7ffd9d357990: 0x00000000 0x00000000 0x5e970730 0x00000000
0x7ffd9d3579a0: 0x65336234 0x30663666 0x90890300 0x79e57be9
0x7ffd9d3579b0: 0x1cd79dbf 0x00000000 0x00000000 0x00000000
0x7ffd9d3579c0: 0x05cec660 0x000055ef 0x9d357fc0 0x00007ffd
0x7ffd9d3579d0: 0x00000000 0x00000000 0x00000000 0x00000000
0x7ffd9d3579e0: 0x9d357ee0 0x00007ffd 0x4b062f26 0x00007fb8
0x7ffd9d3579f0: 0x00000030 0x00000030 0x9d357be8 0x00007ffd
0x7ffd9d357a00: 0x9d357a10 0x00007ffd 0x90890300 0x79e57be9
0x7ffd9d357a10: 0x4b3ea760 0x00007fb8 0x07a51260 0x000055ef
0x7ffd9d357a20: 0x4b3eb8c0 0x00007fb8 0x4b0891bd 0x00007fb8
0x7ffd9d357a30: 0x00000000 0x00000000 0x4b3ea760 0x00007fb8
0x7ffd9d357a40: 0x00000d68 0x00000000 0x00000169 0x00000000
0x7ffd9d357a50: 0x07a51260 0x000055ef 0x4b08af51 0x00007fb8
0x7ffd9d357a60: 0x4b3e62a0 0x00007fb8 0x4b3ea760 0x00007fb8
0x7ffd9d357a70: 0x0000000a 0x00000000 0x05cec660 0x000055ef
0x7ffd9d357a80: 0x9d357fc0 0x00007ffd 0x00000000 0x00000000
0x7ffd9d357a90: 0x00000000 0x00000000 0x4b08b403 0x00007fb8
0x7ffd9d357aa0: 0x4b3ea760 0x00007fb8 0x9d357ee0 0x00007ffd
0x7ffd9d357ab0: 0x05cec660 0x000055ef 0x4b0808f5 0x00007fb8
0x7ffd9d357ac0: 0x00000000 0x00000000 0x05cec824 0x000055ef
(gdb) x/100xw $rsp
0x7ffd9d357ad0: 0x9d357fc8 0x00007ffd 0x9d357b10 0x00000002
0x7ffd9d357ae0: 0x78383025 0x3830252e 0x30252e78 0x252e7838
0x7ffd9d357af0: 0x2e783830 0x78383025 0x3830252e 0x30252e78
0x7ffd9d357b00: 0x252e7838 0x2e783830 0x78383025 0x3830252e
0x7ffd9d357b10: 0x30252e78 0x252e7838 0x2e783830 0x78383025
0x7ffd9d357b20: 0x3830252e 0x30252e78 0x252e7838 0x2e783830
0x7ffd9d357b30: 0x78383025 0x3830252e 0x30252e78 0x252e7838
0x7ffd9d357b40: 0x2e783830 0x78383025 0x3830252e 0x30252e78
0x7ffd9d357b50: 0x252e7838 0x2e783830 0x78383025 0x3830252e
0x7ffd9d357b60: 0x30252e78 0x252e7838 0x2e783830 0x78383025
0x7ffd9d357b70: 0x3830252e 0x30252e78 0x252e7838 0x2e783830
0x7ffd9d357b80: 0x78383025 0x3830252e 0x30252e78 0x252e7838
0x7ffd9d357b90: 0x2e783830 0x78383025 0x3830252e 0x30252e78
0x7ffd9d357ba0: 0x252e7838 0x2e783830 0x4b618d00 0x00007fb8
0x7ffd9d357bb0: 0x4b5fd000 0x00007fb8 0x00000000 0x00000000
0x7ffd9d357bc0: 0x9d357c80 0x00007ffd 0x00000000 0x00000000
0x7ffd9d357bd0: 0x00000000 0x00000000 0x00000000 0x00000000
0x7ffd9d357be0: 0x4b3ef6f0 0x00007fb8 0x4b6184c8 0x00007fb8
0x7ffd9d357bf0: 0x9d357c80 0x00007ffd 0x4b3ef000 0x00007fb8
0x7ffd9d357c00: 0x4b3ef914 0x00007fb8 0x4b3ef3c0 0x00007fb8
0x7ffd9d357c10: 0x4b617048 0x00007fb8 0x00000000 0x00000000
0x7ffd9d357c20: 0x00000000 0x00000000 0x4b6179f0 0x00007fb8
0x7ffd9d357c30: 0x4b0030e8 0x00007fb8 0x00000000 0x00000000
0x7ffd9d357c40: 0x4b3efa00 0x00007fb8 0x00000480 0x00000000
0x7ffd9d357c50: 0x00000027 0x00000000 0x00000000 0x00000000
出现在“%08x”之前的值。在错误的方式输出中,出现在低于“%08x”的地址中。价值观。为什么?格式字符串应该位于堆栈的顶部。
出现在“%08x”之后的值。错误方式输出中的值出现在比“%08x”更高的地址中。价值观。所以在前面的堆栈中。
为什么会这样?输出不应该从格式字符串值开始还是之后?
此外,在书中,它不会在“%08x”之后打印值。价值观。但有些是在我的情况下打印的。输出中的某些值甚至不在堆栈中,例如4b16c3a0。
解决方案
我必须反对你正在做的事情。您专注于 C 中的安全漏洞,但对语言本身没有深入了解。这是一种沮丧的练习。作为证据,我提出你提出的关于练习的每个问题都是通过理解printf (3) 来回答的,而不是堆栈漏洞。
perl 行的输出(的内容argv[1]
)以, 开头%08x.%08x.%08x.%08x.%08x
。那是一个格式字符串。每个%08x
人都在寻找一个进一步的printf
参数,一个以十六进制表示形式打印的整数。通常,你可能会做类似的事情,
int a = 'B';
printf( "%02x\n", a );
它比《银河系漫游指南》中的计算机快42倍。
您所做的是传递一个带有零参数的长格式字符串。 printf (3) 不知道它传递了多少个参数;它必须从格式字符串中推断出它们。您的格式字符串告诉 printf 打印一长串整数。由于没有提供任何内容,它会在“堆栈上”(无论它们应该在哪里)查找它们。您打印废话是因为这些内存位置的内容是不可预测的。或者,无论如何,不是由你定义的。
在“好”的情况下,格式字符串是"%s"
,声明您提供的一个字符串类型的参数。效果更好,是的。
现在大多数编译器都特别注意 printf。如果格式字符串不是编译时常量,它们会产生警告,并且它们可以验证每个参数的类型是否与其对应的格式说明符相符。因此,只需使用编译器的功能并注意其诊断,就可以使您书中的整个章节变得毫无意义。
推荐阅读
- hornetq - 带有 HornetQ 服务器的 ActiveMQ Artemis 客户端库
- reactjs - 使用 `styles` 属性或 `className` 属性 + `mergeStyleSets` 的样式组件?
- javascript - 如何从数据对象中获取价值
- dynamics-crm - 如何在 Dynamics 365 中设计实体内的功能区(标签)?
- jenkins - 如何在 groovy 脚本中添加参数以将文件传递给 Jenkins 作业
- c - 有什么方法可以实现 system() 功能
- python - 使用 PyPDF2 将从 pdf 文件中提取的页面数据转换为 csv 文件
- r - 如何组合选定的行并为其他行切片?
- haskell - 获取图像的高度和宽度
- r - ggplot图中的天数