首页 > 解决方案 > 理解格式字符串利用的困难

问题描述

我正在读一本书,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

再次,为什么AAAA41414141. 据我了解,该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

标签: cformat-stringstack-memory

解决方案


我必须反对你正在做的事情。您专注于 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。如果格式字符串不是编译时常量,它们会产生警告,并且它们可以验证每个参数的类型是否与其对应的格式说明符相符。因此,只需使用编译器的功能并注意其诊断,就可以使您书中的整个章节变得毫无意义。


推荐阅读