【发布时间】:2020-07-27 19:16:00
【问题描述】:
我正在阅读一本书,《黑客:利用的艺术》第 2 版,我正在阅读格式字符串漏洞的章节。我多次阅读该章节,但即使使用谷歌搜索,我也无法清楚地理解它。
所以,书中有这个易受攻击的代码:
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 执行相同操作,因此第二个的值位于比第一个更高的内存地址,最后返回到printf函数的堆栈帧中位于较低内存地址的AAAA的值。
我使用$(perl -e 'print "%08x."x40') 作为参数调试了第一个示例。我运行: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。
【问题讨论】:
-
您是否尝试过在调试器中查看堆栈内存?您引用的这本书的部分内容和您发布的来源的全部意义在于证明您永远不应该允许不受信任的用户将格式字符串输入到您的代码中。当作者谈到堆栈时,它是一个抽象模型。映射到内存的方式取决于架构。栈顶总是最后推送的东西,它可能高于或低于之前推送的东西,具体取决于架构。
-
我调试了,我更糊涂了。我编辑了问题以包括调试。
-
您知道变量参数是如何工作的,以及参数传递在您的平台中是如何工作的吗?您可能有兴趣查找术语“调用约定”。请注意,
printf("%s", string")逐字复制string,而printf(string)将string作为格式字符串传递,因此它会解析并执行其中的任何格式指令。 -
为什么要在 Perl 中这样做?你有一个 C 标签,没有 perl 标签。 Perl 不是 C。它是一种解释型语言,堆栈上可能存在各种运行时垃圾。我没有看到你在哪里编译了任何东西。你在什么架构上运行?
-
查找该术语可能会帮助您获得更正式的想法,但您已经在您所说的事情上走在了正确的轨道上。请记住,即使
printf有自己的帧,堆栈本身也会与调用者共享。
标签: c format-string stack-memory