格式字符串漏洞以segfault结尾

时间:2015-10-28 07:25:40

标签: c path formatting environment-variables exploit

我正在阅读“黑客 - 剥削艺术”一书。

这是我用于利用格式字符串的代码的简化版本。

/* 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的地址,但程序仍然崩溃......

感谢帮助。

3 个答案:

答案 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)。