首页 > 解决方案 > C如何分配内存?

问题描述

我试图了解 GDB 的工作原理以及内存是如何分配的。当我运行以下命令时,假设将 72A写入内存,但是当我在内存中计数时,它只写入 68 A。然后在写入B的内存之前有4个字节的随机内存。当我A在打印语句中计算's时,它显示72 A's。

0xbffff080: 0x14    0x84    0x04    0x08    0x41    0x41    0x41    0x41
0xbffff088: 0x42    0x42    0x42    0x42    0x42    0x42    0x42    0x42

完整的命令如下。

(gdb) run $( python -c "print('A'*72+'BBBB')" )
Starting program: /home/ubuntu/Desktop/test $( python -c "print('A'*72+'BBBB')" )

Breakpoint 2, 0x08048473 in getName (
    name=0xbffff32c 'A' <repeats 72 times>, "BBBB") at sample1.c:7
7       printf("Your name is: %s \n", myName);
(gdb) c
Continuing.
Your name is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB 

Program received signal SIGSEGV, Segmentation fault.
0xbffff32c in ?? ()
(gdb) x/150xb $sp-140
0xbffff038: 0x50    0xf0    0xff    0xbf    0x54    0x82    0x04    0x08
0xbffff040: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff048: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff050: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff058: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff060: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff068: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff070: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff078: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff080: 0x14    0x84    0x04    0x08    0x41    0x41    0x41    0x41
0xbffff088: 0x42    0x42    0x42    0x42    0x42    0x42    0x42    0x42
0xbffff090: 0x2c    0xf3    0xff    0xbf    0x00    0xf0    0xff    0xb7

当我进行进一步测试并添加额外的 4 个字节(4 个C)时,它会在内存和 print 语句中正确显示。

(gdb) run $( python -c "print('A'*72+'BBBB'+'CCCC')" )
Starting program: /home/ubuntu/Desktop/test $( python -c "print('A'*72+'BBBB'+'CCCC')" )

Breakpoint 2, 0x08048473 in getName (name=0xbffff300 "") at sample1.c:7
7       printf("Your name is: %s \n", myName);
(gdb) c
Continuing.
Your name is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC 

Program received signal SIGSEGV, Segmentation fault.
0x43434343 in ?? ()
(gdb) x/150xb $sp-140
0xbffff02c: 0x54    0x82    0x04    0x08    0x41    0x41    0x41    0x41
0xbffff034: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff03c: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff044: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff04c: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff054: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff05c: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff064: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff06c: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff074: 0x41    0x41    0x41    0x41    0x42    0x42    0x42    0x42
0xbffff07c: 0x43    0x43    0x43    0x43    0x00    0xf3    0xff    0xbf
0xbffff084: 0x00    0xf0    0xff    0xb7    0xab    0x84

这是代码:

#include <stdio.h>
#include <string.h>

void getName (char* name) {
    char myName[64];
    strcpy(myName, name);
    printf("Your name is: %s \n", myName);
}

int main (int argc, char* argv[]) {
    getName(argv[1]);
    return 0;
}

反汇编getName显示缓冲区添加了 88 个字节:

Reading symbols from test...done.
(gdb) disas getName
Dump of assembler code for function getName:
   0x0804844d <+0>: push   %ebp
   0x0804844e <+1>: mov    %esp,%ebp
   0x08048450 <+3>: sub    $0x58,%esp
   0x08048453 <+6>: mov    0x8(%ebp),%eax
   0x08048456 <+9>: mov    %eax,0x4(%esp)
   0x0804845a <+13>:    lea    -0x48(%ebp),%eax
   0x0804845d <+16>:    mov    %eax,(%esp)
   0x08048460 <+19>:    call   0x8048320 <strcpy@plt>
   0x08048465 <+24>:    lea    -0x48(%ebp),%eax
   0x08048468 <+27>:    mov    %eax,0x4(%esp)
   0x0804846c <+31>:    movl   $0x8048530,(%esp)
   0x08048473 <+38>:    call   0x8048310 <printf@plt>
   0x08048478 <+43>:    leave  
   0x08048479 <+44>:    ret    
End of assembler dump.

标签: gccassemblyx86gdbexploit

解决方案


由于效率低下,未优化的代码可能会在堆栈上看到额外的填充,但大多数情况下,填充是编译器试图对齐堆栈上的数据的结果。GCC 通常会尝试在可被 16 整除的地址上分配数组。

推送 EBP 后,分配了 0x58 个字节(88 个字节)。由于这条指令,我们可以看到缓冲区从 EBP-0x48 开始:

lea    -0x48(%ebp),%eax

然后使用该地址EBP-0x48来设置堆栈上的参数,以调用strcpyprintf。0x48 = 72 字节,尽管缓冲区是 64 字节。还有另外 8 个字节的填充。为什么那里有填充物?因为编译器试图确保myName缓冲区的开头位于 16 字节边界上。

GCC 可以跟踪堆栈上的内容,但是有关对齐的重要信息来自调用约定(64 位 System V ABI),该约定说在调用函数时(在这种情况下getName)堆栈必须是16 字节对齐。该call指令将 4 个字节压入返回地址,然后将 EBP 压入额外的 4 个字节。编译器在 PUSH EBP 之后知道它错位了 8 个字节。64 + 8 字节的填充 + EBP 的 4 + 4 返回地址 = 80。80 可以被 16 整除 (16*5=80)。8 个字节的使用不是任意的。

在 GDB 输出中,您可以看到myName数组从以 . 结尾的十六进制地址开始0。任何以 16 结尾的十六进制地址0都可以被 16 整除,您可以看到缓冲区从 0xbffff040 开始:

0xbffff038: 0x50    0xf0    0xff    0xbf    0x54    0x82    0x04    0x08
0xbffff040: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41

综上所述,如果您要覆盖返回地址,它将与开头的偏移量myName相等,等于 64(数组大小)+ 8(填充)+ 4(堆栈上的 EBP)= 76 字节。在到达可以替换返回地址的点之前,您必须写入 76 个字节的数据。

注意:您可能想知道为什么该myname数组在堆栈下方有额外的 16 个字节(88-72=16 个字节)。该空间是编译器放置函数调用的值的地方strcpyprintf并确保进行的函数调用具有 16 字节对齐的堆栈以符合 64 位 System V ABI。


myName 中间数据异常的原因

我通过准确复制您在我自己的 Ubuntu 14.04 系统上看到的内容来确认以下观察结果。

您还想知道当您插入 72A和 4B时,缓冲区中有 4 个意外字节:

0xbffff080:[0x14    0x84    0x04    0x08]   0x41    0x41    0x41    0x41
0xbffff088: 0x42    0x42    0x42    0x42    0x42    0x42    0x42    0x42

我用 . 标记了 4 个字节[]。您是对的,您可能期望这 4 个字节与其他字节一样0x41(The letter A)。发生的事情是,尽管您在命令行上输入的是 76 个字符 (72+4) ,但在末尾strcpy附加了一个 NUL( \0) 作为第 77 个字符。这用 0 覆盖了返回地址的低字节!您使用该c命令在断点后继续运行。调试器在遇到分段错误时终止。发生的事情是RET指令没有返回到您期望的main位置,它返回到内存中稍低的位置,因为 NUL 字节被写入返回地址。碰巧你没有看到的是所有执行完之后的指令RET将数据放回堆栈。这包括将 32 位数据写入曾经的myName数组中。

当您编写 72 A、 4B和 4C时,您最终覆盖了返回地址,并且当您尝试在 0x43434343 处开始执行代码CCCC时出现分段错误,如下所示:RET

0x43434343 in ?? ()

0x43434343 不是您具有执行权限的有效地址,因此出现故障。由于RET未能执行更多代码,程序没有机会覆盖myName数组。这就解释了为什么缓冲区没有像之前的测试那样被覆盖。


推荐阅读