自学内容网 自学内容网

协程4 --- 一个特殊的栈溢出例子

代码

先看下面这个程序流程:

有个长度位24的字符数组buffer,前面16个字符初始化。
attack函数的地址复制到后面8个字符(编译成64位程序,指针大小为8Byte)。
打印信息:do Something
调用doSomething,函数内调用了copy函数拷贝到temp变量中,但是注意temp变量长度只有16。
打印信息:do next

提问:do next会打印出来吗

#include <stdio.h>
#include <stdlib.h>

#define BUFFER_LEN 24

void attack()
{
    printf("Hello. I will close your program.\n");
    exit(1);
}

void copy(char* src, char* dest, int n)
{
    for (int i = 0; i < n; ++i)
    {
        dest[i] = src[i];
    }
}

void doSomething(char* str, int n)
{
    // 字符串拷贝
    char temp[16];
    copy(str, temp, n);
}

int main()
{
    char buffer[BUFFER_LEN] =
    {
        'w', 'o', 'l', 'd',
        'a', 'b', 'a', 'b', 'a', 'b',
        'a', 'b', 'a', 'b', 'a', 'b'
    };

    // 将函数注册到字符串最后八个字节
    int n = BUFFER_LEN - 8;
    for (int i = 0; i < 8; ++i)
    {
        buffer[n+i] = (char)((((long)(&attack)) >> (i*8)) & 0xff);
    }

    printf("do Something\n");
    // 运行程序
    doSomething(buffer, BUFFER_LEN);
    // 继续往下执行
    printf("do next\n");

    return 0;
}

运行结果

注意这边用了-O1优化,-fno-stack-protector禁用堆栈保护。
在这里插入图片描述

分析

分析之前先普及一下几点知识

三个寄存器:
rip指向要运行的代码
rbp指向栈底
rsp指向栈顶

程序运行时候的内存分布(当然这个大概是Linux 0.11的,现在的不长这样,不过大体是类似的):
在这里插入图片描述

这边注意一下栈是往下生长的

objdump -d stackattack 看一下汇编

gcc -O1 -o stackattack stackattack.c -g -fno-stack-protector
objdump -d stackattack

000000000040117f <main>:
  40117f:       48 83 ec 28             sub    $0x28,%rsp // 栈中开辟临时变量空间
  401183:       48 c7 44 24 10 00 00    movq   $0x0,0x10(%rsp)
  40118a:       00 00 
  40118c:       c6 04 24 77             movb   $0x77,(%rsp)
  401190:       c6 44 24 01 6f          movb   $0x6f,0x1(%rsp)
  401195:       c6 44 24 02 6c          movb   $0x6c,0x2(%rsp)
  40119a:       c6 44 24 03 64          movb   $0x64,0x3(%rsp)
  40119f:       c6 44 24 04 61          movb   $0x61,0x4(%rsp)
  4011a4:       c6 44 24 05 62          movb   $0x62,0x5(%rsp)
  4011a9:       c6 44 24 06 61          movb   $0x61,0x6(%rsp)
  4011ae:       c6 44 24 07 62          movb   $0x62,0x7(%rsp)
  4011b3:       c6 44 24 08 61          movb   $0x61,0x8(%rsp)
  4011b8:       c6 44 24 09 62          movb   $0x62,0x9(%rsp)
  4011bd:       c6 44 24 0a 61          movb   $0x61,0xa(%rsp)
  4011c2:       c6 44 24 0b 62          movb   $0x62,0xb(%rsp)
  4011c7:       c6 44 24 0c 61          movb   $0x61,0xc(%rsp)
  4011cc:       c6 44 24 0d 62          movb   $0x62,0xd(%rsp)
  4011d1:       c6 44 24 0e 61          movb   $0x61,0xe(%rsp)
  4011d6:       c6 44 24 0f 62          movb   $0x62,0xf(%rsp) // 赋值

  4011db:       b8 00 00 00 00          mov    $0x0,%eax
  4011e0:       be 32 11 40 00          mov    $0x401132,%esi // attack函数地址
  4011e5:       8d 0c c5 00 00 00 00    lea    0x0(,%rax,8),%ecx // i*8
  4011ec:       48 89 f2                mov    %rsi,%rdx
  4011ef:       48 d3 fa                sar    %cl,%rdx // 算数右移
  4011f2:       88 54 04 10             mov    %dl,0x10(%rsp,%rax,1) // dl就是rdx & 0xff 写入目标字符串
  4011f6:       48 83 c0 01             add    $0x1,%rax // ++i
  4011fa:       48 83 f8 08             cmp    $0x8,%rax
  4011fe:       75 e5                   jne    4011e5 <main+0x66>

  401200:       bf 30 20 40 00          mov    $0x402030,%edi
  401205:       e8 26 fe ff ff          callq  401030 <puts@plt> // printf

  40120a:       be 18 00 00 00          mov    $0x18,%esi // 参数n
  40120f:       48 89 e7                mov    %rsp,%rdi // 参数str
  401212:       e8 55 ff ff ff          callq  40116c <doSomething> // 调用doSomething

  401217:       bf 3d 20 40 00          mov    $0x40203d,%edi
  40121c:       e8 0f fe ff ff          callq  401030 <puts@plt>  // printf

  401221:       b8 00 00 00 00          mov    $0x0,%eax
  401226:       48 83 c4 28             add    $0x28,%rsp
  40122a:       c3                      retq   
  40122b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

000000000040116c <doSomething>:
  40116c:       48 83 ec 10             sub    $0x10,%rsp // 栈中开辟临时变量空间

  401170:       89 f2                   mov    %esi,%edx // 参数 n
  401172:       48 89 e6                mov    %rsp,%rsi // desc
  401175:       e8 d0 ff ff ff          callq  40114a <copy> // 调用copy

  40117a:       48 83 c4 10             add    $0x10,%rsp
  40117e:       c3                      retq

000000000040114a <copy>:
  40114a:       85 d2                   test   %edx,%edx
  40114c:       7e 1d                   jle    40116b <copy+0x21> // 如果参数n是0 直接返回

  40114e:       8d 4a ff                lea    -0x1(%rdx),%ecx // 参数n-1 因为从0开始循环
  401151:       b8 00 00 00 00          mov    $0x0,%eax // i = 0
  401156:       eb 03                   jmp    40115b <copy+0x11>
  401158:       48 89 d0                mov    %rdx,%rax
  40115b:       0f b6 14 07             movzbl (%rdi,%rax,1),%edx // src[n] -> 寄存器
  40115f:       88 14 06                mov    %dl,(%rsi,%rax,1) // 寄存器 -> desc[n]
  401162:       48 8d 50 01             lea    0x1(%rax),%rdx // ++i
  401166:       48 39 c8                cmp    %rcx,%rax
  401169:       75 ed                   jne    401158 <copy+0xe>

  40116b:       c3                      retq
  • -fno-stack-protector:禁用堆栈保护
  • 如果不优化用O0,不仅会push rip还会push rbp
  • 第一行sub $0x28,%rsp 在栈中开辟临时变量空间,其中0x28中是 (字符串长度24,16字节对齐到0x20) + (printf的0x8)
  • printf会在第一次调用的时候用malloc申请缓冲区,malloc会在分配的地址前预留2*sizeof(size_t)的空间维护malloc_chunk信息 。

如果看不懂汇编的可以看这个堆栈变换的示意图:
在这里插入图片描述
一开始attack函数的地址被放在buffer数组后8位,调用了doSomething后压栈了riptemp数组变量,结果字符串copybuffer数组比temp数组长的后8位也被拷贝过去了,由于关闭了栈保护,把原来的rip覆盖了。doSomething函数结束后就调用到attack函数了。


原文地址:https://blog.csdn.net/qq_40571533/article/details/126973920

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!