为什么不同的Linux系统上格式字符串在堆栈上的位置如此不同?

时间:2011-10-22 14:04:31

标签: c

当你调用像printf这样的函数时,formatstring和arguments被压入堆栈。如果省略参数但是在格式字符串中使用“%x”或“%s”或“%n”指定它们,则可以访问(读取或写入)formatstring。在一个系统上我测试,格式字符串是第四个参数。另一个是超过200。

例如,我得到了以下程序,它容易受到formatstring exploit的攻击,并包含以下语句:

printf(userSuppliedString);

不,我想阅读特定的地址。例如。 0xbffffdd7。 我用以下方式称呼它:

./fmt_vuln $(printf "\xd7\xfd\xff\xbf")%08x.%08x.%08x.%s

在此示例中,格式字符串是第四个参数(“%s”)。所以%s将采用格式字符串的开头。因为这是我们指定的地址,所以将打印此地址的内容。

现在在这台机器上,formatstring是第四个paraemter。但在其他Linux系统上,它的东西完全不同。 为什么会这样?

2 个答案:

答案 0 :(得分:6)

  

当你调用像printf这样的函数时,formattring和   参数被压入堆栈。如果你省略参数但是   用格式字符串中的“%x”或“%s”或“%n”指定它们   访问(读取或写入)formatstring。在一个系统上我测试,   格式字符串是第4个参数。在另一个它超出了   第200个。

不,也许你误解了。

当您使用一个参数(格式字符串)调用printf时,指向格式字符串的指针将被压入堆栈。这是一个char *这个指针可以指向内存中的任何位置 - printf只是告诉它并将该内存位置作为格式字符串读取。

在通常的一个参数情况下,将字符串文字传递给printf ("hello world!");编译器将文本hello world放在内存中的某个位置,并生成指向它的指针以传递给printf。然后它执行任何调用约定它应该为函数调用做的事情 - 例如在x86上它将指针推送到堆栈。 Printf然后从堆栈中读取它的第一个参数并且很高兴!

在通常的n参数情况下,字符串文字和指针会发生同样的事情。对于函数调用,编译器传递每个值。再次使用x86(因为推送比描述具有复杂参数传递方案的ARM更容易描述)这些值从右到左被推送到堆栈。因此,如果您对printf ("%d, %s, %d", x, name, y);的调用被推送到堆栈,则命名为x,最后是格式字符串。

现在,在printf中我们读取了我们的第一个参数(从堆栈中获取)。它是指向char *的{​​{1}}。我们可以阅读这个,然后 - 知道编译器如何传递参数,我们可以读取被推入堆栈的三件事 - 我们再次感到高兴!

格式字符串漏洞的工作原理是错误地认为printf具有的信念和编译器所具有的信念。

我们可以通过调用由于向printf传递错误数量的参数而导致的未定义行为来显示它。在调用中"%d, %s, %d"编译器不会推送与printf ("%s"); printf预期用于实现char *指令相对应的参数。但是 - 因为printf不知道编译器没有这样做,所以无论如何都会在堆栈上查找参数。它从堆栈中提取一个未定义的值,并尝试读取它指向的字符串。

在您的情况下,您允许将任意格式字符串传递给printf。这些肯定与预期的参数数量和传递的参数数量不匹配,因此printf读取堆栈 - 它充满了垃圾。

如果你很幸运 - 你可以操纵这个垃圾指向你控制的东西 - 并且可以用它来阅读你没想到的信息。如果您可以欺骗%n参数指向您控制的某个位置,则可以使用打印的字符数写入该内存位置。

所以 - 考虑到这个描述,我无法找到解析你的问题的方法。也许你可以更清楚,我可以更新我的答案?

答案 1 :(得分:1)

./fmt_vuln $(printf "\xd7\xfd\xff\xbf")%08x.%08x.%08x.%s
     

在此示例中,格式字符串是第四个参数(“%s”)。

不,不是真的。问题是你没有访问printf的第四个参数,而是访问其调用函数中的局部变量或参数(或者在堆栈的上方)。因此,它完全取决于调用函数的代码。为了演示它在386上做了什么:

Breakpoint 1, __printf (format=0xbffff543 "%p") at printf.c:29
29      printf.c: Adresář nebo soubor neexistuje.
        in printf.c
(gdb) x/120a $ebp

Description:    $esp            return addr             fmtstring       parameters

0xbffff2d8:     0xbffff2f8      0x80483fd <main+25>     0xbffff543      0xb7ff1310
0xbffff2e8:     0x804842b <__libc_csu_init+11>  0xb7fb7ff4      0x8048420 <__libc_csu_init>     0x0
0xbffff2f8:     0xbffff378      0xb7e78e46 <__libc_start_main+230>      0x2     0xbffff3a4
0xbffff308:     0xbffff3b0      0xb7fe1860      0xb7ff7411      0xffffffff
0xbffff318:     0xb7ffeff4      0x8048254       0x1     0xbffff360
0xbffff328:     0xb7ff0996      0xb7fffac0      0xb7fe1b58      0xb7fb7ff4
0xbffff338:     0x0     0x0     0xbffff378      0xa32ae5c4
0xbffff348:     0x93d0f3d4      0x0     0x0     0x0
0xbffff358:     0x2     0x8048330 <_start>      0x0     0xb7ff65b0
0xbffff368:     0xb7e78d6b <__libc_start_main+11>       0xb7ffeff4      0x2     0x8048330 <_start>
0xbffff378:     0x0     0x8048351 <_start+33>   0x80483e4 <main>        0x2
0xbffff388:     0xbffff3a4      0x8048420 <__libc_csu_init>     0x8048410 <__libc_csu_fini>     0xb7ff1310
0xbffff398:     0xbffff39c      0xb7fff908      0x2     0xbffff539
0xbffff3a8:     0xbffff543      0x0     0xbffff546      0xbffff55a
0xbffff3b8:     0xbffff56a      0xbffff581      0xbffff58c      0xbffff5dc
0xbffff3c8:     0xbffff5f3      0xbffff654      0xbffff66f      0xbffff689

如您所见,格式字符串仅存在于内存中,位于由argv指向的libc运行时初始化的区域中。你必须更好地研究你正在攻击的代码。