为什么即使在易受攻击的缓冲区之前声明函数指针也会被覆盖?

逆向工程 C linux 部件 x86
2021-06-14 05:16:39

我现在正在做io-wargames的乐趣,level3:

我确实理解为什么此代码中存在堆栈溢出(strlen(argv[1])),但我不明白的是为什么它会溢出函数指针functionpointer

functionpointer之前char buffer[50];在堆栈上声明过,所以它是如何覆盖它的???

这是主要的易受攻击代码:

int main(int argc, char **argv, char **envp)
{
        void (*functionpointer)(void) = bad;
        char buffer[50];

        if(argc != 2 || strlen(argv[1]) < 4)
                return 0;

        memcpy(buffer, argv[1], strlen(argv[1]));
        memset(buffer, 0, strlen(argv[1]) - 4);

        printf("This is exciting we're going to %p\n", functionpointer);
        functionpointer();

        return 0;
}

这是shell利用stackoverflow:

level3@io:~$ /levels/level03 11111111
This is exciting we're going to 0x80484a4
I'm so sorry, you're at 0x80484a4 and you want to be at 0x8048474
level3@io:~$ /levels/level03 111111111111111111111111111111111111111111111111111111111111111111111111111111111
This is exciting we're going to 0x31313100
Segmentation fault

这是objump -d可执行文件的:

080484c8 <main>:
 80484c8:       55                      push   %ebp
 80484c9:       89 e5                   mov    %esp,%ebp
 80484cb:       83 ec 78                sub    $0x78,%esp
 80484ce:       83 e4 f0                and    $0xfffffff0,%esp
 80484d1:       b8 00 00 00 00          mov    $0x0,%eax
 80484d6:       29 c4                   sub    %eax,%esp
 80484d8:       c7 45 f4 a4 84 04 08    movl   $0x80484a4,-0xc(%ebp)
 80484df:       83 7d 08 02             cmpl   $0x2,0x8(%ebp)
 80484e3:       75 17                   jne    80484fc <main+0x34>
 80484e5:       8b 45 0c                mov    0xc(%ebp),%eax
 80484e8:       83 c0 04                add    $0x4,%eax
 80484eb:       8b 00                   mov    (%eax),%eax
 80484ed:       89 04 24                mov    %eax,(%esp)
 80484f0:       e8 a7 fe ff ff          call   804839c <strlen@plt>
 80484f5:       83 f8 03                cmp    $0x3,%eax
 80484f8:       76 02                   jbe    80484fc <main+0x34>
 80484fa:       eb 09                   jmp    8048505 <main+0x3d>
 80484fc:       c7 45 a4 00 00 00 00    movl   $0x0,-0x5c(%ebp)
 8048503:       eb 74                   jmp    8048579 <main+0xb1>
 8048505:       8b 45 0c                mov    0xc(%ebp),%eax
 8048508:       83 c0 04                add    $0x4,%eax
 804850b:       8b 00                   mov    (%eax),%eax
 804850d:       89 04 24                mov    %eax,(%esp)
 8048510:       e8 87 fe ff ff          call   804839c <strlen@plt>
 8048515:       89 44 24 08             mov    %eax,0x8(%esp)
 8048519:       8b 45 0c                mov    0xc(%ebp),%eax
 804851c:       83 c0 04                add    $0x4,%eax
 804851f:       8b 00                   mov    (%eax),%eax
 8048521:       89 44 24 04             mov    %eax,0x4(%esp)
 8048525:       8d 45 a8                lea    -0x58(%ebp),%eax
 8048528:       89 04 24                mov    %eax,(%esp)
 804852b:       e8 5c fe ff ff          call   804838c <memcpy@plt>
 8048530:       8b 45 0c                mov    0xc(%ebp),%eax
 8048533:       83 c0 04                add    $0x4,%eax
 8048536:       8b 00                   mov    (%eax),%eax
 8048538:       89 04 24                mov    %eax,(%esp)
 804853b:       e8 5c fe ff ff          call   804839c <strlen@plt>
 8048540:       83 e8 04                sub    $0x4,%eax
 8048543:       89 44 24 08             mov    %eax,0x8(%esp)
 8048547:       c7 44 24 04 00 00 00    movl   $0x0,0x4(%esp)
 804854e:       00 
 804854f:       8d 45 a8                lea    -0x58(%ebp),%eax
 8048552:       89 04 24                mov    %eax,(%esp)
 8048555:       e8 02 fe ff ff          call   804835c <memset@plt>
 804855a:       8b 45 f4                mov    -0xc(%ebp),%eax
 804855d:       89 44 24 04             mov    %eax,0x4(%esp)
 8048561:       c7 04 24 c0 86 04 08    movl   $0x80486c0,(%esp)
 8048568:       e8 3f fe ff ff          call   80483ac <printf@plt>
 804856d:       8b 45 f4                mov    -0xc(%ebp),%eax
 8048570:       ff d0                   call   *%eax
 8048572:       c7 45 a4 00 00 00 00    movl   $0x0,-0x5c(%ebp)
 8048579:       8b 45 a4                mov    -0x5c(%ebp),%eax
 804857c:       c9                      leave  
 804857d:       c3                      ret    
 804857e:       90                      nop
 804857f:       90                      nop

我看到编译器在 main 的 prolog 函数帧0x78字节中为本地 main 函数变量保留了字节。

3个回答

函数指针在 char buffer[50] 之前声明;在堆栈上,所以它怎么会覆盖它???

堆栈中对象的顺序是实现定义的。C 没有提到任何堆栈,堆栈增长的方向也是实现定义的(通常它向下增长,但在某些系统中它向上增长)。

在您的情况下functionpointer,可能是先放,然后是buffer. 由于在您的系统中堆栈向下增长,这允许您functionpointer在溢出时覆盖buffer

编译器确实将函数指针放在缓冲区之后。

在反汇编中,检查memcpy调用:

8048525:  lea    -0x58(%ebp),%eax
8048528:  mov    %eax,(%esp)
804852b:  call   804838c <memcpy@plt>

memcpy(缓冲区地址)的第一个参数是 at [esp+0],您可以看到 的值ebp-0x58被放置在那里。

接下来是函数末尾的函数调用:

804856d:  mov    -0xc(%ebp),%eax
8048570:  call   *%eax

可以看到跳转到的地址是从[ebp-0xc]字符缓冲区开始后的0x4c(76)字节处加载的

这是 IDA 的堆栈布局,希望能让事情更清楚:

-00000058 buffer          db 76 dup(?)
-0000000C functionpointer dd ?
-00000008 var_8           dd ?
-00000004 var_4           dd ?
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)
+00000008 argc            dd ?
+0000000C argv            dd ?

最左边一列中的偏移量与 ebp 相关。内存地址向下增加,所以很明显,写入太多数据buffer会覆盖函数指针(然后是返回地址)。

MSVC 实际上使用了针对此类攻击的缓解措施 - 它重新排序字符缓冲区以放置在所有其他变量之后:

在此处输入图片说明

为什么即使在易受攻击的缓冲区之前声明函数指针也会被覆盖?

在易受攻击的代码中,声明的顺序是:

void (*functionpointer)(void) = bad;  
char buffer[50];

汇编代码向我们表明,函数指针变量位于ebp-0xc缓冲ebp-0x58

这证明堆栈在这个系统中向下增长(到更低的地址),因为缓冲区被放置在比函数指针变量更低的地址处

另一个证明该系统中堆栈向下增长的证据是以下指令,该指令通过减去 来分配所需的空间esp

80484cb:       83 ec 78                sub    $0x78,%esp


现在从位于 的字节开始memcpy复制num字节ebp-0x58,然后继续递增。

添加1ebp-0x58使得它ebp-0x57,所以如果num足够长,memcpy将覆盖位于函数指针ebp-0xc

ebp持有一个地址,比方说0x00400000从您最终到达ebp-0x58的地址开始0x003FFFA8递增的地址也是如此ebp-0xc(0x003FFFF4)

0x003FFFA8     buffer
...
0x003FFFF4     function pointer
0x003FFFF8     
0x003FFFFC    
0x00400000     saved ebp
0x00400004     saved return address