简单缓冲区溢出原理

时间:2022-07-23
本文章向大家介绍简单缓冲区溢出原理,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

本篇原创作者:Rj45

背景

什么是缓冲区溢出?这里我借某台栈溢出靶机里面的第一道题目来解释缓冲区溢出的原理。

可以看到靶机里面有两份权限不同的文件,而我目前拿到的shell是 level0

审计levelOne.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv) {
    uid_t uid = geteuid();
    setresuid(uid, uid, uid);
    long key = 0x12345678;
    char buf[32];
    strcpy(buf, argv[1]);
    printf("Buf is: %sn", buf);
    printf("Key is: 0x%08xn", key);
    if(key == 0x42424242) {
        execve("/bin/sh", 0, 0);
    }
    else {
        printf("%sn", "Sorry try again...");
    }
    return 0;
}

1、审计:可以看到,程序一开始声明并初始化了key=0x12345678, 并声明了32字节的buf, 然后通过strcpy函数将第一个参数拷贝到buf中, 接着判断key是否为0x42424242,如是则获得shell。(levelOne的权限为level1) key已经初始化为0x12345678,如何才能将其修改为0x42424242?

2、溢出:如何将key修改为0x42424242?--通过缓冲区溢出覆盖key值为0x42424242。什么是缓冲区溢出?在样例程序中声明了一段32字节的buf,此为缓冲区, 当通过strcpy函数将输入的函数第一个参数拷贝到缓冲区的时候,由于strcpy函数为危险函数,其不会对操作对象进行任何检查,故在输入数据不超过32个字节时不会发生任何情况, 但当输入的数据超过32个字节的时候,就会发生溢出,也即所谓的缓冲区溢出。

3、覆盖:根据前面我们学习到的内容,一个程序在载入内存后,其栈区会存在函数的各种变量、参数、栈针、返回地址等,并且各种数据是相邻分布的。这就意味着,一个存在缓冲区溢出的程序,在精准控制溢出范围的情况下,可以精准覆盖内存栈区中某些特殊位置的数据。这就为利用构造了条件,也即在本样例程序中的覆盖key值为0x42424242。

4、危险函数:显而易见,在缓冲区溢出的过程中,最关键的就是strcpy函数。那么还有哪些类似strcpy的危险函数呢?

可以看到这些危险函数集中为IO函数。

反汇编levelOne

1、命令

objdump -d levelOne -M intel

2、反汇编 下面为样例程序的反汇编情况:

000011e9 <main>:
    11e9:    8d 4c 24 04             lea    ecx,[esp+0x4]
    11ed:    83 e4 f0                and    esp,0xfffffff0
    11f0:    ff 71 fc                push   DWORD PTR [ecx-0x4]
    11f3:    55                      push   ebp
    11f4:    89 e5                   mov    ebp,esp
    11f6:    56                      push   esi
    11f7:    53                      push   ebx
    11f8:    51                      push   ecx
    11f9:    83 ec 3c                sub    esp,0x3c
    11fc:    e8 ef fe ff ff          call   10f0 <__x86.get_pc_thunk.bx>
    1201:    81 c3 ff 2d 00 00       add    ebx,0x2dff
    1207:    89 ce                   mov    esi,ecx
    1209:    e8 42 fe ff ff          call   1050 <geteuid@plt>
    120e:    89 45 e4                mov    DWORD PTR [ebp-0x1c],eax
    1211:    83 ec 04                sub    esp,0x4
    1214:    ff 75 e4                push   DWORD PTR [ebp-0x1c]
    1217:    ff 75 e4                push   DWORD PTR [ebp-0x1c]
    121a:    ff 75 e4                push   DWORD PTR [ebp-0x1c]
    121d:    e8 0e fe ff ff          call   1030 <setresuid@plt>
    1222:    83 c4 10                add    esp,0x10     //调整栈帧
    1225:    c7 45 e0 78 56 34 12    mov    DWORD PTR [ebp-0x20],0x12345678  //声明并初始化了一个变量,地址为ebp-0x20,数据为0x12345678
    122c:    8b 46 04                mov    eax,DWORD PTR [esi+0x4]
    122f:    83 c0 04                add    eax,0x4
    1232:    8b 00                   mov    eax,DWORD PTR [eax]
    1234:    83 ec 08                sub    esp,0x8
    1237:    50                      push   eax
    1238:    8d 45 c0                lea    eax,[ebp-0x40]
    123b:    50                      push   eax
    123c:    e8 1f fe ff ff          call   1060 <strcpy@plt>        //将输入点数据拷贝到ebp-0x40
    1241:    83 c4 10                add    esp,0x10             //调整栈帧
    1244:    83 ec 08                sub    esp,0x8
    1247:    8d 45 c0                lea    eax,[ebp-0x40]
    124a:    50                      push   eax
    124b:    8d 83 08 e0 ff ff       lea    eax,[ebx-0x1ff8]
    1251:    50                      push   eax
    1252:    e8 e9 fd ff ff          call   1040 <printf@plt>
    1257:    83 c4 10                add    esp,0x10
    125a:    83 ec 08                sub    esp,0x8
    125d:    ff 75 e0                push   DWORD PTR [ebp-0x20]
    1260:    8d 83 14 e0 ff ff       lea    eax,[ebx-0x1fec]
    1266:    50                      push   eax
    1267:    e8 d4 fd ff ff          call   1040 <printf@plt>
    126c:    83 c4 10                add    esp,0x10
    126f:    81 7d e0 42 42 42 42    cmp    DWORD PTR [ebp-0x20],0x42424242      //判断ebp-0x20处的变量是否为0x42424242
    1276:    75 18                   jne    1290 <main+0xa7>     //如果相同则跳转到后门函数
    1278:    83 ec 04                sub    esp,0x4
    127b:    6a 00                   push   0x0
    127d:    6a 00                   push   0x0
    127f:    8d 83 24 e0 ff ff       lea    eax,[ebx-0x1fdc]         //将’/bin/sh’压入栈中,作为函数参数
    1285:    50                      push   eax              //将0压入栈中作为函数参数
    1286:    e8 05 fe ff ff          call   1090 <execve@plt>        //后门
    128b:    83 c4 10                add    esp,0x10
    128e:    eb 12                   jmp    12a2 <main+0xb9>
    1290:    83 ec 0c                sub    esp,0xc
    1293:    8d 83 2c e0 ff ff       lea    eax,[ebx-0x1fd4]
    1299:    50                      push   eax
    129a:    e8 d1 fd ff ff          call   1070 <puts@plt>
    129f:    83 c4 10                add    esp,0x10
    12a2:    b8 00 00 00 00          mov    eax,0x0
    12a7:    8d 65 f4                lea    esp,[ebp-0xc]
    12aa:    59                      pop    ecx
    12ab:    5b                      pop    ebx
    12ac:    5e                      pop    esi
    12ad:    5d                      pop    ebp
    12ae:    8d 61 fc                lea    esp,[ecx-0x4]
    12b1:    c3                      ret
    12b2:    66 90                   xchg   ax,ax
    12b4:    66 90                   xchg   ax,ax
    12b6:    66 90                   xchg   ax,ax
    12b8:    66 90                   xchg   ax,ax
    12ba:    66 90                   xchg   ax,ax
    12bc:    66 90                   xchg   ax,ax
    12be:    66 90                   xchg   ax,ax

栈中的情况如下图:

pwn

如此当填充满32个字节的数据后,在填充4个B,即达成利用条件,进入后门函数。