【问题标题】:Format string exploit ends in segfault格式字符串利用以段错误结束
【发布时间】:2015-10-28 07:25:40
【问题描述】:

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

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

/* 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的地址,但是程序还是崩溃了……

感谢您的帮助。

【问题讨论】:

    标签: c path formatting environment-variables exploit


    【解决方案1】:

    出于安全原因,并非计算机中的所有进程都共享相同的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之间有一个特殊的硬件,将地址从“虚拟地址”转换为“物理地址”,它被称为内存管理单元(简称MMU)。如果程序请求地址 0x1000 的值,MMU 可能会“重新映射”0x1000 到 0x8000。如果地址 0x1000 在所有读取和写入到达 RAM 之前总是被 0x8000 替换,这似乎是一个毫无意义的操作。该程序仍然以完全相同的方式运行......那么有什么大不了的?

    重要的是现在程序 1 和 2 无法访问彼此的数据。 MMU 可以配置为不存在程序 1 可以读取的包含程序 2 变量之一的地址。这种“映射”对于每个进程(大部分)都是唯一的,并且由操作系统配置。

    这是一个 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 单元!这个绝妙的发明被称为“虚拟记忆”。如果 MMU 以不同方式映射它们的内存,我们说 2 个进程具有不同的“地址空间”。操作系统决定这些映射并配置 MMU 以遵守它们,从而使进程彼此“隔离”。考虑 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 可能未映射,导致 SEGFAULT.

    所以坏消息是,我认为没有任何方法可以用代码“修复”这个问题。做你想做的事情会给你一个段错误或随机的无用数据。要获取您感兴趣的数据,您需要查看 MMU 配置设置并更改它们,除非您以提升的权限级别运行,否则不允许这样做。

    在我们分开之前,值得注意的是,进程之间可能存在一些共享地址,以便在两个进程之间来回传递数据或访问共享软件库。也就是说,对于几个不同的进程,0x1000 将转换为 0x5000。

    或者我不知道你在说什么。我并没有真正关注./getenv PATH ./fmt_vuln

    【讨论】:

    • 你提到的那一行其实是getenvaddr程序,可以在这里找到:code.google.com/p/protostar-solutions/source/browse/Stack+6/…
    • 其次,你知道为什么在书中他们做了同样的事情并获得了 PATH 的价值吗?他们从最后一条评论中寻找程序中的地址,并完全按照我所做的(当然是他们得到的地址)。
    • 此代码高度依赖系统。我不确定你的系统,但在我的系统上,如果我多次运行 get_env,每次都会得到不同的地址。
    • 这听起来像是一个逃避现实的答案,但我怀疑如果您使用的是本书的第一版(写于 2003 年),用于编写本书的原始系统可能会有所不同比你的。
    • 是的,在我的电脑上,我每次在 get_env 上得到不同的结果。正如你所建议的,我认为它实际上是依赖于 ststem 的。无论如何,感谢您的帮助和详细的解释!
    【解决方案2】:

    您使用 64 位系统,因此您的地址有 8 个字节。

    看看那个地址0x7ffe2a673d84,它只有6个字节。这意味着较高的两个字节为零。但是您不能简单地将零字节提供为像 \xff\xaa\xbb\x00 这样的十六进制字符串,因为程序会将零字节解释为字符串的结尾。

    您需要使用 32 位系统来进行实验,x64 架构不允许您进行该漏洞利用。

    【讨论】:

      【解决方案3】:

      偶然发现同一件事(读同一本书)。

      getenv_addr 不是超级可靠。我通过在fmt_vuln.c 中显示PATH 的确切地址来作弊,并添加:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-15
        • 2011-01-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多