进程的虚拟地址 space 的哪些部分是可覆盖的?
What parts of a process' virtual address space are overwriteable?
例如,假设缓冲区不是在堆栈的相反方向增长,而是在相同的方向增长。如果我有一个包含字符串 "Hello world" 的字符缓冲区,而不是将 'H' 放在最低地址,而是放在最高地址,依此类推。
如果复制到缓冲区的输入字符串溢出,它无法覆盖函数的 return 地址,但肯定还有其他可以覆盖的内容。我的问题是——如果输入字符串足够长,哪些内容可以被覆盖?堆和栈之间是否存在可以被覆盖的库函数?堆变量可以被覆盖吗?我假设 data 和 bss 段中的变量可以被覆盖,但是 text 段是否有写保护?
您问题的答案完全取决于所使用的操作系统以及硬件架构。操作系统以某种方式布置逻辑内存,架构有时也会为特定目的保留(非常低的)内存。
需要了解的一件事是,传统进程可以访问其整个逻辑内存 space,但通常很少使用这种容量。您所描述的最可能的影响是您将尝试访问一些未分配的内存,并且您将得到一个段错误作为响应,从而使您的程序崩溃。
就是说,您当然可以修改这些其他内存段,但是这样做会发生什么取决于它们的 read/write 权限。例如,你在学校学习的典型内存布局是:
Low memory to high memory:
.text - program code
.data - initialized static variables
.bss - uninitialized static variables
.heap - grows up
memory map segments - dynamic libraries
.stack - grows down
默认情况下,.text 段被标记为只读/可执行,因此如果您尝试写入 .text 内存位置,您将遇到段错误。可以将 .text 更改为可写,但这通常是一个糟糕的想法。
默认情况下,.data、.bss、.heap 和 .stack 段都是 readable/writeable,因此您可以覆盖这些段而不会出现任何程序错误。
内存映射段也都有自己的处理权限。其中一些段是可写的,大多数不是(因此写入它们会产生段错误)。
最后要注意的是,大多数现代操作系统都会随机化这些段的位置,从而使黑客更难搞定。这可能会在不同段之间引入间隙(如果您尝试访问它们,这将再次导致段错误)。
在 Linux 上,您可以使用命令 pmap
打印出进程的内存映射。以下是此程序在 vim 实例上的输出:
10636: vim hello.text
0000000000400000 2112K r-x-- vim
000000000080f000 4K r---- vim
0000000000810000 88K rw--- vim
0000000000826000 56K rw--- [ anon ]
0000000000851000 2228K rw--- [ anon ]
00007f7df24c6000 8212K r--s- passwd
00007f7df2ccb000 32K r-x-- libnss_sss.so.2
00007f7df2cd3000 2044K ----- libnss_sss.so.2
00007f7df2ed2000 4K r---- libnss_sss.so.2
00007f7df2ed3000 4K rw--- libnss_sss.so.2
00007f7df2ed4000 48K r-x-- libnss_files-2.17.so
00007f7df2ee0000 2044K ----- libnss_files-2.17.so
00007f7df30df000 4K r---- libnss_files-2.17.so
00007f7df30e0000 4K rw--- libnss_files-2.17.so
00007f7df30e1000 24K rw--- [ anon ]
00007f7df30e7000 103580K r---- locale-archive
00007f7df960e000 8K r-x-- libfreebl3.so
00007f7df9610000 2044K ----- libfreebl3.so
00007f7df980f000 4K r---- libfreebl3.so
00007f7df9810000 4K rw--- libfreebl3.so
00007f7df9811000 8K r-x-- libutil-2.17.so
00007f7df9813000 2044K ----- libutil-2.17.so
00007f7df9a12000 4K r---- libutil-2.17.so
00007f7df9a13000 4K rw--- libutil-2.17.so
00007f7df9a14000 32K r-x-- libcrypt-2.17.so
00007f7df9a1c000 2044K ----- libcrypt-2.17.so
00007f7df9c1b000 4K r---- libcrypt-2.17.so
00007f7df9c1c000 4K rw--- libcrypt-2.17.so
00007f7df9c1d000 184K rw--- [ anon ]
00007f7df9c4b000 88K r-x-- libnsl-2.17.so
00007f7df9c61000 2044K ----- libnsl-2.17.so
00007f7df9e60000 4K r---- libnsl-2.17.so
00007f7df9e61000 4K rw--- libnsl-2.17.so
00007f7df9e62000 8K rw--- [ anon ]
00007f7df9e64000 88K r-x-- libresolv-2.17.so
00007f7df9e7a000 2048K ----- libresolv-2.17.so
00007f7dfa07a000 4K r---- libresolv-2.17.so
00007f7dfa07b000 4K rw--- libresolv-2.17.so
00007f7dfa07c000 8K rw--- [ anon ]
00007f7dfa07e000 152K r-x-- libncurses.so.5.9
00007f7dfa0a4000 2044K ----- libncurses.so.5.9
00007f7dfa2a3000 4K r---- libncurses.so.5.9
00007f7dfa2a4000 4K rw--- libncurses.so.5.9
00007f7dfa2a5000 16K r-x-- libattr.so.1.1.0
00007f7dfa2a9000 2044K ----- libattr.so.1.1.0
00007f7dfa4a8000 4K r---- libattr.so.1.1.0
00007f7dfa4a9000 4K rw--- libattr.so.1.1.0
00007f7dfa4aa000 144K r-x-- liblzma.so.5.0.99
00007f7dfa4ce000 2044K ----- liblzma.so.5.0.99
00007f7dfa6cd000 4K r---- liblzma.so.5.0.99
00007f7dfa6ce000 4K rw--- liblzma.so.5.0.99
00007f7dfa6cf000 384K r-x-- libpcre.so.1.2.0
00007f7dfa72f000 2044K ----- libpcre.so.1.2.0
00007f7dfa92e000 4K r---- libpcre.so.1.2.0
00007f7dfa92f000 4K rw--- libpcre.so.1.2.0
00007f7dfa930000 1756K r-x-- libc-2.17.so
00007f7dfaae7000 2048K ----- libc-2.17.so
00007f7dface7000 16K r---- libc-2.17.so
00007f7dfaceb000 8K rw--- libc-2.17.so
00007f7dfaced000 20K rw--- [ anon ]
00007f7dfacf2000 88K r-x-- libpthread-2.17.so
00007f7dfad08000 2048K ----- libpthread-2.17.so
00007f7dfaf08000 4K r---- libpthread-2.17.so
00007f7dfaf09000 4K rw--- libpthread-2.17.so
00007f7dfaf0a000 16K rw--- [ anon ]
00007f7dfaf0e000 1548K r-x-- libperl.so
00007f7dfb091000 2044K ----- libperl.so
00007f7dfb290000 16K r---- libperl.so
00007f7dfb294000 24K rw--- libperl.so
00007f7dfb29a000 4K rw--- [ anon ]
00007f7dfb29b000 12K r-x-- libdl-2.17.so
00007f7dfb29e000 2044K ----- libdl-2.17.so
00007f7dfb49d000 4K r---- libdl-2.17.so
00007f7dfb49e000 4K rw--- libdl-2.17.so
00007f7dfb49f000 20K r-x-- libgpm.so.2.1.0
00007f7dfb4a4000 2048K ----- libgpm.so.2.1.0
00007f7dfb6a4000 4K r---- libgpm.so.2.1.0
00007f7dfb6a5000 4K rw--- libgpm.so.2.1.0
00007f7dfb6a6000 28K r-x-- libacl.so.1.1.0
00007f7dfb6ad000 2048K ----- libacl.so.1.1.0
00007f7dfb8ad000 4K r---- libacl.so.1.1.0
00007f7dfb8ae000 4K rw--- libacl.so.1.1.0
00007f7dfb8af000 148K r-x-- libtinfo.so.5.9
00007f7dfb8d4000 2048K ----- libtinfo.so.5.9
00007f7dfbad4000 16K r---- libtinfo.so.5.9
00007f7dfbad8000 4K rw--- libtinfo.so.5.9
00007f7dfbad9000 132K r-x-- libselinux.so.1
00007f7dfbafa000 2048K ----- libselinux.so.1
00007f7dfbcfa000 4K r---- libselinux.so.1
00007f7dfbcfb000 4K rw--- libselinux.so.1
00007f7dfbcfc000 8K rw--- [ anon ]
00007f7dfbcfe000 1028K r-x-- libm-2.17.so
00007f7dfbdff000 2044K ----- libm-2.17.so
00007f7dfbffe000 4K r---- libm-2.17.so
00007f7dfbfff000 4K rw--- libm-2.17.so
00007f7dfc000000 132K r-x-- ld-2.17.so
00007f7dfc1f8000 40K rw--- [ anon ]
00007f7dfc220000 4K rw--- [ anon ]
00007f7dfc221000 4K r---- ld-2.17.so
00007f7dfc222000 4K rw--- ld-2.17.so
00007f7dfc223000 4K rw--- [ anon ]
00007ffcb46e7000 132K rw--- [ stack ]
00007ffcb475f000 8K r-x-- [ anon ]
ffffffffff600000 4K r-x-- [ anon ]
total 163772K
从 0x851000 开始的段实际上是堆的开始(pmap 会告诉您更详细的报告模式,但更详细的模式不适合)。
进程在内存中的布局因系统而异。此答案涵盖 x86_64 个处理器下的 Linux。
有一篇很好的文章说明了 Linux 个进程 here 的内存布局。
如果缓冲区是局部变量,那么它将与其他局部变量一起在堆栈上。如果缓冲区溢出,您可能遇到的第一件事是同一函数中的其他局部变量。
当您到达堆栈的末尾时,在下一个使用的内存段之前有一个随机大小的偏移量。如果您继续写入此地址 space,您将触发段错误(因为该地址 space 未映射到任何物理 RAM)。
假设您设法跳过随机偏移而没有崩溃,并继续覆盖,接下来它可能命中的是内存映射段。该段包含文件映射,包括用于将动态共享库映射到地址 space 的映射,以及匿名映射。动态库将是只读的,但如果进程有任何 RW 映射,您可能会覆盖其中的数据。
此段之后是另一个随机偏移量,然后才到达堆。同样,如果您尝试写入随机偏移量的地址 space,您将触发崩溃。
堆下方是另一个随机偏移量,然后是 BSS、数据,最后是文本段。 BSS 和数据中的静态变量可能会被覆盖。文本段不应该是可写的。
您可以使用 pmap 命令检查进程的内存映射。
我认为您的问题反映了对操作系统工作方式的根本误解。 "buffers" 和 "stack" 之类的东西往往不由操作系统定义。
操作系统将内存分为内核区和用户区(有些系统还有额外的保护区)。
用户区的布局通常由链接器定义。链接器创建可执行文件,指示加载器如何设置地址 space。各种链接器具有不同级别的控制。通常,默认链接器设置将程序部分分组为:
-Read/execute
-Read/no执行
-Read/write/initialized
-Read/write/demand零
您可以使用一些链接器创建具有这些属性的多个程序段。
你问:
"If I have a character buffer containing the string "Hello world",不是'H'放在最低地址,而是放在最高地址,以此类推。"
在范诺依曼机器中,内存与其使用无关。同一内存块可以同时解释为字符串、浮点数、整数或指令。您可以按照您想要的任何顺序放置您的字母,但大多数软件库不会以相反的顺序识别它们。如果您自己的库可以处理向后存储的字符串,请自行努力。
"My question is -- if the input string was long enough, what things could be overwritten?"
可以是任何东西。
"Are there library functions that exist between the heap and the stack that could be overwritten?"
这取决于您的链接器做了什么。
"Can heap variables be overwritten?"
堆可以被覆盖。
"我假设data和bss段中的变量可以被覆盖,但是text段有写保护吗?
一般来说,是的。
例如,假设缓冲区不是在堆栈的相反方向增长,而是在相同的方向增长。如果我有一个包含字符串 "Hello world" 的字符缓冲区,而不是将 'H' 放在最低地址,而是放在最高地址,依此类推。
如果复制到缓冲区的输入字符串溢出,它无法覆盖函数的 return 地址,但肯定还有其他可以覆盖的内容。我的问题是——如果输入字符串足够长,哪些内容可以被覆盖?堆和栈之间是否存在可以被覆盖的库函数?堆变量可以被覆盖吗?我假设 data 和 bss 段中的变量可以被覆盖,但是 text 段是否有写保护?
您问题的答案完全取决于所使用的操作系统以及硬件架构。操作系统以某种方式布置逻辑内存,架构有时也会为特定目的保留(非常低的)内存。
需要了解的一件事是,传统进程可以访问其整个逻辑内存 space,但通常很少使用这种容量。您所描述的最可能的影响是您将尝试访问一些未分配的内存,并且您将得到一个段错误作为响应,从而使您的程序崩溃。
就是说,您当然可以修改这些其他内存段,但是这样做会发生什么取决于它们的 read/write 权限。例如,你在学校学习的典型内存布局是:
Low memory to high memory:
.text - program code
.data - initialized static variables
.bss - uninitialized static variables
.heap - grows up
memory map segments - dynamic libraries
.stack - grows down
默认情况下,.text 段被标记为只读/可执行,因此如果您尝试写入 .text 内存位置,您将遇到段错误。可以将 .text 更改为可写,但这通常是一个糟糕的想法。
默认情况下,.data、.bss、.heap 和 .stack 段都是 readable/writeable,因此您可以覆盖这些段而不会出现任何程序错误。
内存映射段也都有自己的处理权限。其中一些段是可写的,大多数不是(因此写入它们会产生段错误)。
最后要注意的是,大多数现代操作系统都会随机化这些段的位置,从而使黑客更难搞定。这可能会在不同段之间引入间隙(如果您尝试访问它们,这将再次导致段错误)。
在 Linux 上,您可以使用命令 pmap
打印出进程的内存映射。以下是此程序在 vim 实例上的输出:
10636: vim hello.text
0000000000400000 2112K r-x-- vim
000000000080f000 4K r---- vim
0000000000810000 88K rw--- vim
0000000000826000 56K rw--- [ anon ]
0000000000851000 2228K rw--- [ anon ]
00007f7df24c6000 8212K r--s- passwd
00007f7df2ccb000 32K r-x-- libnss_sss.so.2
00007f7df2cd3000 2044K ----- libnss_sss.so.2
00007f7df2ed2000 4K r---- libnss_sss.so.2
00007f7df2ed3000 4K rw--- libnss_sss.so.2
00007f7df2ed4000 48K r-x-- libnss_files-2.17.so
00007f7df2ee0000 2044K ----- libnss_files-2.17.so
00007f7df30df000 4K r---- libnss_files-2.17.so
00007f7df30e0000 4K rw--- libnss_files-2.17.so
00007f7df30e1000 24K rw--- [ anon ]
00007f7df30e7000 103580K r---- locale-archive
00007f7df960e000 8K r-x-- libfreebl3.so
00007f7df9610000 2044K ----- libfreebl3.so
00007f7df980f000 4K r---- libfreebl3.so
00007f7df9810000 4K rw--- libfreebl3.so
00007f7df9811000 8K r-x-- libutil-2.17.so
00007f7df9813000 2044K ----- libutil-2.17.so
00007f7df9a12000 4K r---- libutil-2.17.so
00007f7df9a13000 4K rw--- libutil-2.17.so
00007f7df9a14000 32K r-x-- libcrypt-2.17.so
00007f7df9a1c000 2044K ----- libcrypt-2.17.so
00007f7df9c1b000 4K r---- libcrypt-2.17.so
00007f7df9c1c000 4K rw--- libcrypt-2.17.so
00007f7df9c1d000 184K rw--- [ anon ]
00007f7df9c4b000 88K r-x-- libnsl-2.17.so
00007f7df9c61000 2044K ----- libnsl-2.17.so
00007f7df9e60000 4K r---- libnsl-2.17.so
00007f7df9e61000 4K rw--- libnsl-2.17.so
00007f7df9e62000 8K rw--- [ anon ]
00007f7df9e64000 88K r-x-- libresolv-2.17.so
00007f7df9e7a000 2048K ----- libresolv-2.17.so
00007f7dfa07a000 4K r---- libresolv-2.17.so
00007f7dfa07b000 4K rw--- libresolv-2.17.so
00007f7dfa07c000 8K rw--- [ anon ]
00007f7dfa07e000 152K r-x-- libncurses.so.5.9
00007f7dfa0a4000 2044K ----- libncurses.so.5.9
00007f7dfa2a3000 4K r---- libncurses.so.5.9
00007f7dfa2a4000 4K rw--- libncurses.so.5.9
00007f7dfa2a5000 16K r-x-- libattr.so.1.1.0
00007f7dfa2a9000 2044K ----- libattr.so.1.1.0
00007f7dfa4a8000 4K r---- libattr.so.1.1.0
00007f7dfa4a9000 4K rw--- libattr.so.1.1.0
00007f7dfa4aa000 144K r-x-- liblzma.so.5.0.99
00007f7dfa4ce000 2044K ----- liblzma.so.5.0.99
00007f7dfa6cd000 4K r---- liblzma.so.5.0.99
00007f7dfa6ce000 4K rw--- liblzma.so.5.0.99
00007f7dfa6cf000 384K r-x-- libpcre.so.1.2.0
00007f7dfa72f000 2044K ----- libpcre.so.1.2.0
00007f7dfa92e000 4K r---- libpcre.so.1.2.0
00007f7dfa92f000 4K rw--- libpcre.so.1.2.0
00007f7dfa930000 1756K r-x-- libc-2.17.so
00007f7dfaae7000 2048K ----- libc-2.17.so
00007f7dface7000 16K r---- libc-2.17.so
00007f7dfaceb000 8K rw--- libc-2.17.so
00007f7dfaced000 20K rw--- [ anon ]
00007f7dfacf2000 88K r-x-- libpthread-2.17.so
00007f7dfad08000 2048K ----- libpthread-2.17.so
00007f7dfaf08000 4K r---- libpthread-2.17.so
00007f7dfaf09000 4K rw--- libpthread-2.17.so
00007f7dfaf0a000 16K rw--- [ anon ]
00007f7dfaf0e000 1548K r-x-- libperl.so
00007f7dfb091000 2044K ----- libperl.so
00007f7dfb290000 16K r---- libperl.so
00007f7dfb294000 24K rw--- libperl.so
00007f7dfb29a000 4K rw--- [ anon ]
00007f7dfb29b000 12K r-x-- libdl-2.17.so
00007f7dfb29e000 2044K ----- libdl-2.17.so
00007f7dfb49d000 4K r---- libdl-2.17.so
00007f7dfb49e000 4K rw--- libdl-2.17.so
00007f7dfb49f000 20K r-x-- libgpm.so.2.1.0
00007f7dfb4a4000 2048K ----- libgpm.so.2.1.0
00007f7dfb6a4000 4K r---- libgpm.so.2.1.0
00007f7dfb6a5000 4K rw--- libgpm.so.2.1.0
00007f7dfb6a6000 28K r-x-- libacl.so.1.1.0
00007f7dfb6ad000 2048K ----- libacl.so.1.1.0
00007f7dfb8ad000 4K r---- libacl.so.1.1.0
00007f7dfb8ae000 4K rw--- libacl.so.1.1.0
00007f7dfb8af000 148K r-x-- libtinfo.so.5.9
00007f7dfb8d4000 2048K ----- libtinfo.so.5.9
00007f7dfbad4000 16K r---- libtinfo.so.5.9
00007f7dfbad8000 4K rw--- libtinfo.so.5.9
00007f7dfbad9000 132K r-x-- libselinux.so.1
00007f7dfbafa000 2048K ----- libselinux.so.1
00007f7dfbcfa000 4K r---- libselinux.so.1
00007f7dfbcfb000 4K rw--- libselinux.so.1
00007f7dfbcfc000 8K rw--- [ anon ]
00007f7dfbcfe000 1028K r-x-- libm-2.17.so
00007f7dfbdff000 2044K ----- libm-2.17.so
00007f7dfbffe000 4K r---- libm-2.17.so
00007f7dfbfff000 4K rw--- libm-2.17.so
00007f7dfc000000 132K r-x-- ld-2.17.so
00007f7dfc1f8000 40K rw--- [ anon ]
00007f7dfc220000 4K rw--- [ anon ]
00007f7dfc221000 4K r---- ld-2.17.so
00007f7dfc222000 4K rw--- ld-2.17.so
00007f7dfc223000 4K rw--- [ anon ]
00007ffcb46e7000 132K rw--- [ stack ]
00007ffcb475f000 8K r-x-- [ anon ]
ffffffffff600000 4K r-x-- [ anon ]
total 163772K
从 0x851000 开始的段实际上是堆的开始(pmap 会告诉您更详细的报告模式,但更详细的模式不适合)。
进程在内存中的布局因系统而异。此答案涵盖 x86_64 个处理器下的 Linux。
有一篇很好的文章说明了 Linux 个进程 here 的内存布局。
如果缓冲区是局部变量,那么它将与其他局部变量一起在堆栈上。如果缓冲区溢出,您可能遇到的第一件事是同一函数中的其他局部变量。
当您到达堆栈的末尾时,在下一个使用的内存段之前有一个随机大小的偏移量。如果您继续写入此地址 space,您将触发段错误(因为该地址 space 未映射到任何物理 RAM)。
假设您设法跳过随机偏移而没有崩溃,并继续覆盖,接下来它可能命中的是内存映射段。该段包含文件映射,包括用于将动态共享库映射到地址 space 的映射,以及匿名映射。动态库将是只读的,但如果进程有任何 RW 映射,您可能会覆盖其中的数据。
此段之后是另一个随机偏移量,然后才到达堆。同样,如果您尝试写入随机偏移量的地址 space,您将触发崩溃。
堆下方是另一个随机偏移量,然后是 BSS、数据,最后是文本段。 BSS 和数据中的静态变量可能会被覆盖。文本段不应该是可写的。
您可以使用 pmap 命令检查进程的内存映射。
我认为您的问题反映了对操作系统工作方式的根本误解。 "buffers" 和 "stack" 之类的东西往往不由操作系统定义。
操作系统将内存分为内核区和用户区(有些系统还有额外的保护区)。
用户区的布局通常由链接器定义。链接器创建可执行文件,指示加载器如何设置地址 space。各种链接器具有不同级别的控制。通常,默认链接器设置将程序部分分组为:
-Read/execute
-Read/no执行
-Read/write/initialized
-Read/write/demand零
您可以使用一些链接器创建具有这些属性的多个程序段。
你问:
"If I have a character buffer containing the string "Hello world",不是'H'放在最低地址,而是放在最高地址,以此类推。"
在范诺依曼机器中,内存与其使用无关。同一内存块可以同时解释为字符串、浮点数、整数或指令。您可以按照您想要的任何顺序放置您的字母,但大多数软件库不会以相反的顺序识别它们。如果您自己的库可以处理向后存储的字符串,请自行努力。
"My question is -- if the input string was long enough, what things could be overwritten?"
可以是任何东西。
"Are there library functions that exist between the heap and the stack that could be overwritten?"
这取决于您的链接器做了什么。
"Can heap variables be overwritten?"
堆可以被覆盖。
"我假设data和bss段中的变量可以被覆盖,但是text段有写保护吗?
一般来说,是的。